Smart contracts – best practices


See also: Software development – best practices

Writing the smart contracts

How to convert between a role and a reference

Problem

You know that any permission (normally defined in definition.permissions) may depend on a role (which can actually be mode = all or mode = any and contain multiple actual roles). It can also contain a requires condition with all_of or any_of fields being the lists of references to be checked (and to be satisfied either simultaneously, for all_of, or to be satisfied if at least a single one reference is satisfied, for any_of). But can you adapt a reference to a role (so, e.g., a mode = any complex role will utilize a reference check)? Or vice versa, can you adapt a role to a reference (so, e.g., a requires.any_of check will utilize a role check)?

Solution

Adapting a role to a reference check

For example, how to adapt an owner role to a reference check in the permission?

Use the following in the reference condition:

this can_play this.owner
Adapting a reference to a role

For example, how to adapt an external_reference reference to the role?

It is less known, but the reference can be used not just inside the permissions, but as an extra check in some role as well!

- role1:
  type: simple
  required:
    any_of: [external_reference]

How to write a “finder keeper” contract: change owner to “anybody”

Problem

Imagine you want to perform a giveaway, and give out some token (or some other contract) to the first person who finds it and registers themselves as an owner. How to do that? Usually you change the contract owner to a specific new owner (address/public key), and after that only the owner is capable of using it; if you register some “nobody” as a new owner, you lose your own ownership right, but then only the registered “nobody” (i.e. noone!) will be able to use it. How to register a contract to “anybody”?

Solution

Registering it to “nobody” is registering to a role (like, SimpleRole) that will never be satisfied. Registering it to “anybody” is, instead, registering it to a SimpleRole that is always satisfied. You can create a SimpleRole that has a bound reference, which is always true; like, ref.origin defined.

Using the smart contracts

How to make an offline “cold wallet”

Problem

For major financial institutions dealing with crypto assets of large worth, it is rather regular to use a “cold wallet” concept, when the primary amount of worth is stored/controlled on an offline/disconnected computer. When such a computer has completely no access to the Internet, it cannot be broken, and the assets cannot be stolen. How to make the similar with Universa?

Solution

Let’s imagine two processes and three primary roles.

Alice is our “Holder”, which uses the cold wallet to keep the assets/smart contracts on a safe offline place. Bob is our “Sender”, who initially sends (and changes the owner) some smart contracts (or even specifically, tokens) to Alice . Charlie is our “Receiver”; at some point Alice wants to send (and in particularly, change the owner) some smart contracts from her offline “cold wallet” to Charlie.

Initial preparations

Alice gets two separate computers; one will act as a “cold wallet”, not connected to internet; one will act as a “hot wallet”, connected to Internet and performing the contract registrations in Universa.

On both computers, she creates a private key, a “cold wallet private key” and a “hot wallet private key” accordingly. She gets the key address of the cold wallet private key; that will be used in smart contracts.

On “hot wallet computer”, she reserves a necessary amount ot UTNs/Us to be able to register the contracts. All these Us should be owned by her “hot wallet private key”.

Receiving the assets

Bob wants to send some assets to Alice:

  • Bob changes the assets to be owned by Alice’s “cold wallet address”, and registers this change in Universa. (Note he doesn’t need any operations from Alice side for that, in particular ones involving her “cold wallet private key”).
  • Bob delivers the smart contracts to Alice’s “hot wallet computer”.
  • On the “hot wallet computer” (which has access to Universa to check the contracts), Alice checks that the smart contracts are approved.
  • After that, Alice transfers the received smart contracts to “cold wallet computer” using some offline means (like, the USB thumb drive).
Sending the assets

Alice wants to send some assets to Charlie:

  • On the “cold wallet computer” (ideally: even on the “hot wallet computer”, causing as little operations on “cold wallet computer” as possible), Alice prepares a new transaction pack containing the ownership change from Alice to Charlier for the assets she is sending (for the tokens, that will be a split-join operation). If it was prepared on the “hot wallet computer”, she transfers it to the “cold wallet computer” (using something like the USB thumb drive). Then she signs it on the “cold wallet computer” with her “cold wallet private key”.
  • Alice delivers this transaction pack to “hot wallet computer” using some offline means (like, the USB thumb drive).
  • On “hot wallet computer”, Alice makes the “compound” containing the Us required to register this transaction; the Us are owned by the “hot wallet private key”. She signs the compound with the “hot wallet private key” (which is nowhere related to the “cold wallet private key”), and registers it in Universa.
  • After successful registration, Alice delivers the signed smart contracts to Charlie (using CryptoCloud-based chat in Web Client, or even some non-Universa means).

Notes

Besides the procedure above, please also take your usual steps to ensure your security.

  • Use and regularly update the antivirus software on “hot wallet computer” (and preferrably on “cold wallet computer too” – you’ll need to update it without connecting it to the Internet though!), to prevent any virus being able to infect your USB thumb drive.
  • The computer used for “hot swap wallet” (and obviously, the computer used for the “cold swap wallet”!) should NOT be used for any non-Universa-related activities, such as (but not limited to) casual Internet browsing, gaming, social networks, etc.

To prevent any viruses substituting the data, you may want to double-check the assets/amount (if tokens)/new owner address just before signing the data on the “cold swap computer”. Please always make sure you are signing the transaction which you do want to perform, not any misgenerated one! Though, the address structure makes it almost impossible for malicious party to generate an address resembling the original address too close; so the substituted address will likely be different significantly.

How to make utility tokens on top of Universa

Problem

Imagine a new service called KTN is going to launch a new blockchain-powered game selling/exchanging some, say, “virtual kittens”; and this game will have an internal currency called “virtual dollars”.

For this purpose, at least two new different kinds of contracts are to be created: VKTN is a smart contract for a kitten, and VUSD is a smart contract for “virtual dollars”.

VUSD is going to be a “utility token”, and, in the KTN service, this will be the primary currency to pay for all blockchain operations – for example, for breeding virtual kittens or changing their owner. Whenever you register something in KTN blockchain, you spend VUSD to pay for that.

It seems somewhat trivial if the KTN service builds up a whole new blockchain from scratch.

graph LR Alice["Alice"] Bob["Bob"] KTN("KTN blockchain") Alice-- sends a kitten -->Bob Alice-. pays VUSD .->KTN

It is a bit less trivial if some existing blockchain like Ethereum is used as a backbone. Because the user also have to pay the Ethereum “gas” fees, so the user has to deal with both VUSD tokens and also obtain some ETH for fees.

graph LR Alice["Alice"] Bob["Bob"] subgraph Blockchain layer KTN("KTN service") Ethereum("Ethereum blockchain") KTN---Ethereum end Alice-- sends a kitten -->Bob Alice-. "pays VUSD" .->KTN Alice-. "pays ETH (spends gas)" .->Ethereum

Can it be done in Universa in a way easier for user?

Solution

Yes!

It can be abstracted in such a way that the user won’t even know they deal with Universa and UTN tokens – and still, UTN tokens will be used to reserve U needed for network functioning>

As the KTN team has anyway to develop the whole stack for that, they will develop a new service for dealing with cross-blockchain exchanges; and similarly to Universa’s URS, let’s think they called it “KURS” (“K for 🐱kittens!”).

In Universa, the amount of energy/resources required to deal with the contract registration is called “U”; in Ethereum, it is “gas”; for KTN, let’s call it “milk”.

So the user has to spend some VUSD to “buy some milk for kittens”. This may be a separate operation, similar to “reserving some U” in Universa, making it possible to reserve a large amount of U in advance; this may be an immediate operation (so in a glimpse, VUSD is spend to “buy some milk” – and only the amount of milk needed for this operation – and the milk is immediately wasted for KTN network functioning – like with Ethereum gas); doesn’t matter, only the whole idea matters.

From user’s viewpoint

So, from the user’s point of view, this will look this way:

sequenceDiagram participant Alice participant Bob participant KTN as KTN Alice->>+Bob: sends a kitten Alice->>+KTN: sends the transaction, pays VUSD for “milk” KTN-->>-Alice: registers the “send a kitten” transaction Bob-->>-Alice: thanks!

But what happens one layer below?

Inside KTN architecture

sequenceDiagram participant Alice participant KURS participant KTN Alice->>+KURS: pays VUSD for milk KURS-->>-Alice: returns “milk” Alice->>+KTN: sends the transaction, provides milk KTN-->>-Alice: registers the “send a kitten” transaction

Just like if the KTN service had created a whole independent blockchain, the user just thinks they spent some VUSD and got some “milk” in return; and they are using “milk” to pay for the network.

But what is most important: now there is only one single place, KURS, that needs to deal with UTN tokens directly; know about them (e.g., purchase them on the exchange) and reserve U!

And note, that even Alice thinks it is KTN service that handles the whole transaction, actually it is the Universa network that handles it.

So let’s dive deeper.

Inside KURS

sequenceDiagram participant Alice participant KURS participant URS Alice->>+KURS: pays VUSD for milk Note over KURS: exchange VUSD internally to UTN KURS->>+URS: reserve some U on behalf of Alice URS-->>-KURS: return some U owned by Alice KURS-->>-Alice: returns “milk” and (behinds the scenes) U

From the UI level, Alice just purchased some milk from KURS. But, behind the scenes, KURS called the UUTN service at URS and reserved some U.

It is KURS who internally maintains the appropriate balance of UTN tokens to handle all the transactions of KTN players in Universa network. It is only KURS who has to purchase the UTN tokens and deal with them directly. And whenever the user thinks they purchase some “milk” (in terms of KTN), the KURS actually reserves some U for them (in terms of Universa).

Thankfully, UUTN service has a feature of reserving the U smart contracts for a different party; so it is KURS whose private key is used to pay for U reservation, but it is Alice whose private key will now own these Us.

So now Alice can “send the transaction” in KTN service; but behind the scenes, she already has got some Us reserved, and can use them to actually send the transaction in Universa network.

sequenceDiagram participant Alice participant KTN participant Universa Alice->>+KTN: (according to UI) sends the transaction, provides milk Alice->>+Universa: (actually) sends the transaction, provides U Universa-->>-Alice: (actually) registers the transaction KTN-->>-Alice: (according to UI) registers the “send a kitten” transaction

How to register multiple revisions in a single transaction

Problem

Have you ever thought about registering multiple revisions in a single transaction?

Usages for this can be numerous:

  1. Batching/caching the multiple general-purpose changes.

    Imagine your smart contract tracks some object changes that happen just way to irregularly: there can be hours or days without a single change in the contract, and then there can be a dozen or a hundred of changes within several seconds.

    Like, you have a hosting service, and each smart contract tracks the user visits of the control panel and administrative actions taken – normally the user won’t visit it for weeks, but then visits it and does many operations at once, signs them with their own private key (and thus each change is externally signed by them) and wants all of them reflected in the Universa distributed ledger.

    If your service has many users doing many operations at once, attempting to register every control panel action in Universa may put a heavy load on your servers, both network load (for each action, you have to send the registration request to Universa) and CPU load (you also have to handle the Universa network responses, wait for the network consensus, postpone registering all subsequent changes until the consensus is reached, etc etc).

    In such a scenario, it makes some sense to not register every contract change, but probably cache and batch it, e.g.: collect all changes until either 1 second elapsed since the latest received change, or 10 seconds elapsed since the earliest non-registered change. This lets your system ensure than the worst-case registration delay is 10 seconds while best-case is still 1 second.

    Note this would also somewhat decreases the cost of the registration comparing to registering multiple changes independently, but only slightly: if you want the most cost-effective registration, you must collect your changes on the middleware level, and make a single new revision with all the changes, if possible.

  2. Batching multiple token (split-join) operations at once.

    Even more specific case for multiple operations are the token split-join operations. You may run a “virtual bank” or “crypto currency exchange”; and you may do many internal operations in it: intra-client transfers, matching the exchange sell/buy orders, etc. As all of these operations are internal and happen in your own backend, you definitely trust them already; the blockchain registrations are intended for public transparency only.

    So, to decrease the network/processing load on your servers, you may want to implement the batching (with best case and worst case) similarly to the previous case. In revision 17 you spend (and sign) some ABC token giving out 127.45 of them to user Alice; in revision 18 you are also giving 223.56 ABC tokens to user Bob; in revision 19 Alice has already returned 785 ABC tokens to you and you join them to the pool,..

  3. Atomic operations with referential triggers.

    Well you know that a referential constraint in your smart contract (most likely, in its definition.references section) may refer to a field in some other contract. This constraint can be used then in any of the permissions in your contract, so some permission can be dependent on an external contract; that external contract acts as a trigger/flag to permit some operations on your contract.

    For example, you are building an estate property registration system, where anyone can register changing ownership of their townhouse or condo. Normally, the definition.permissions.change_owner will contain just the owner, right? But for legal reasons, you are requested to implement the government (or some other arbiter) right to seize any of these estate properties from some state offenders. This seizure can be implemented as easy as changing the change_owner permission to ANY_OF with multiple roles, one being the role of the owner and another being the role of the arbiter/seizure agency.

    But this seizure can have a more complex procedure itself, and have an independent smart contract (with many other incapsulated rules and constraints). For example, each seizure requires a new revision of the smart contract with Origin ID=ABCD1234, and this revision must have a field, say, state.seizure_mode = 1 (in real life, there may be more complex conditions, like, state.seizure_subject containing the public key or the address of the offender whose property is being seized).

    In this case, you may add this “seizure reference” to the definition of your property smart contract (something like: some reference in definition.references has name = arbiter_seizure and where condition containing all_of = ["ref.state.origin == ABCD1234", "ref.state.seizure_mode == 1"]). Thus your change_owner permission will require either to owner to do this, or a seizure contract (with seizure_mode = 1) to be referred (see the “How to convert between a role and a reference” practice if you need to use a role and a reference in the same condition).

    But wait, after the seizure_mode is set in that contract, it will stay set! Wouldn’t it be safer if we set seizure_mode to 1; referred to this contract; and set the seizure_mode back to 0? Ideally, in a single transaction, to prevent any race conditions. Can’t we do a single transaction that makes a new revision of “seizure contract” setting it as needed to allow seizure; make a new revision of the “property contract” referring to this “flag-on” revision; and immediately make a second revision of the “seizure contract” making it “flag-off”?

    Note this “seizure contract” is just an example of some centralized flag that can control some of referential constraints in the definition of your contracts; there may be more uses for it.

Solution

Since version 3.9.1 of the codebase, a single transaction can contain multiple revision changes. Just get them all in a single transaction pack, and register it. That’s it!

How to verify “the contract is yours”/“token balance”

Problem

When writing the custom client software for specific business logic (be that general-purpose contract handling, special-purpose contract handling, or token “wallet”-style contract handling), you may often want to handle/display, for the current user, some contracts as “belonging to the user”, or “controlled by the user”. For the tokens, this may involve calculating the proper token balance, if the tokens “belong to the user”. How to properly test that?

Solution

Depending on the particular business needs, you need to choose the particular aspects you are testing/displaying, among the ones below:

1. Is the contract actually APPROVED by the network right now?

Your client may need to check the item state of the contract more or less often. If it is very sure that the client is the only one who can change/register the contract, it may remember the item state and cache it for a long time; but if it is possible that there are other clients who can alter the same contract – or there are other users/private keys who can do it – you may need to recheck the state regularly and/or on demand.

2. “Belonging” or “Controlling”?

Do you care about the contracts “being owned” by the user, or “being controlled” by them? You need to decide and distinguish. For the typical tokens, this is often virtually the same – the one who is listed as an “owner”, is also the only one who can change the owner, and/or split-join the token. In most complex cases, it may be otherwise:

  1. It is possible to make the smart contracts (or even tokens) for which not just the current owner, but someone else (like the central-issuing authority) can change their owner or split them.
  2. It is even possible to make the smart contract (or tokens) for which the owner user cannot even control the token (change its owner or split it).

To test the “belonging”, it is usually sufficient to test the owner field of the contract. To the test the “controlling”, one may need to try to execute the fake Change Owner operation (as a most basic action to confirm being able to control), create the transaction pack, do not register it, but test for errors.

3. Are you the only one?

No matter if you choose “belonging” or “controlling” on the previous step, it may be important to display if you are the sole user being able to do that, or if some other users may exist.

If you decide to display the “sole belonging”/“sole controlling”:

  • If you chose “belonging”: does the owner field contain the SimpleRole matching your address? Or it is a ListRole? (so it is possible the others may control it too). If it is some other role, it is definitely more complex contract to understand than usual.
  • If you chose “controlling”: the verification may be more complex; but in general you should test whether the ChangeOwner and SplitJoin (if available) permissions (for typical tokens) are currently eventually mapped to the SimpleRole (matching your address).
4. Are you the owner unconditionally?

You may be the owner (belonger) and/or controller of the contract; this may be defined via a SingleRole, or maybe even via a ListRole (so it is a multisig key). But even them, it may happen that the current owner (or the current “controller”, i.e. “ones who has the ChangeOwner and SplitJoin permissions”) owns it with some reference/requirement added. In such a case, if any reference exists linked to the according role (they may be too hard to parse their inner logic fully) – you may want to display that it is likely you can use this contract/token but only on some conditions that you should know. And attempt to create the dummy ChangeOwner/SplitJoin transaction without actually registering it, may verify whether you are currently seem to be able to control it; but as the reference may include time-restricted factors (e.g. you can control it only before some date, or some other date), the subsequent attempt to make the very same SplitJoin transaction may fail even if the first attempt succeeded.