Getting started with Tezos #2: contracts

October 27, 2017
easy cryptocurrency tezos michelson blockchain smart contracts

Introduction

In this article we will look at how to publish contracts to the blockchain and how to use transactions. If you haven’t read it yet, start with the previous article. No blockchain expertise is required, although general programming knowledge would be useful.


Originating contracts

Last time we’ve created a contract that concatenates strings and type checked it, then we’ve ran it in the online REPL and the command line alphanet client. However, you might have noticed that running it this way, we always get the same results – the contract is just running locally and doesn’t have anywhere to store its state. For the contract to utilize the updated storage in the future, we need to publish it to the blockchain. The command to do this is called originate in Tezos, let’s see how to use it:

./alphanet.sh client man | grep originate -B 2 -A 2

The B and A flags stand for before and after respectively, and let us see a number of lines surrounding the target phrase when using grep.

We’d like to originate a contract, so these are the lines we care about:

originate contract (new) for (mgr) transferring (qty) from (src) running (prg) [-fee _]
  [-delegate _] [-force] [-delegatable] [-non-spendable] [-init _]

We can see that the command requires us to specify a few parameters. We should specify a new alias for the originated contract in place of (new), an alias or address of an existing manager for (mgr), the starting balance for (qty) and the source of those funds for (src). Last but not least, the file containing the contract code for (prg).

For some contract in a file ourContract.tz it may look like this:

./alphanet.sh client originate contract ourAlias for my_identity transferring 5 from money running container:ourContract.tz

A manager of a contract is usually the owner – in the default case they are able to spend the money contained in the contract (this can be configured when creating the contract). Since we are just creating a simple contract for ourselves, we can go ahead and specify ourselves as the manager. We can use the default identity my_identity. If everything works, after executing the command we should have a new contract published on the blockchain and its address saved under the ourAlias alias. We specified the starting balance of 5 tez, so there will be 5 tez less in our account with the alias money and there will be 5 tez in the newly originated contract.

We can check this:

./alphanet.sh client get balance for ourAlias
./alphanet.sh client get balance for money

You may be wondering what’s the difference between an account and a contract, since both have addresses and can take part in transactions. Let’s look at the technical white paper (note that this is a different paper than the language specification). Section 3.3 deals with smart contracts.

In lieu of unspent outputs, Tezos uses stateful accounts. When those accounts specify executable code, they are known more generally as contracts. Since an account is a type of contract (one with no executable code), we refer to both as ”contracts” in full generality.

So they are essentially the same. The paper also has more information about the manager of a contract:

Each contract has a “manager”, which in the case of an account is simply the owner. If the contract is flagged as spendable, the manager may spend the funds associated with the contract. In addition, each contract may specify the hash of a public key used to sign or mine blocks in the proof-of-stake protocol. The private key may or may not be controlled by the manager.


Making it run

Ok, let’s do it with our contract we’ve written last time, that appends a string to its storage. If you don’t have it handy, here’s the code:

parameter string;
return string;
storage string;
code {DUP;
      DIP{CDR};
      CAR;
      SWAP;
      CONCAT;
      DUP;
      PAIR}

Save the contract to a file and originate it:

./alphanet.sh client originate contract appendString for my_identity transferring 100 from money running container:appendString.tz -init '"hello"'

You can use any file names or aliases you want, but make sure you replace my example names everywhere. I have an account with a pile of money named money, you can use the default my_account if your money is there. We can specify the initial storage with init.

If the origination seems to have worked, but you are getting a Not Found error when trying to check the balance, you can forget the contract with:

./alphanet.sh client forget contract appendString

Then you can try to originate it again and/or specify a fee to increase the chances of the contract being included in the next block.

We can initiate contract execution by sending a transaction to the contract address. If the contract is an account, the transaction just moves money which isn’t that interesting. If however, the contract contains some code, the money is added to the contract and the code is executed. The transaction can also use an arg flag to specify parameters that will then be used in the contract code.

Let’s see:

./alphanet.sh client transfer 0 from my_account to appendString -arg '"test1"'

The storage should become "hellotest1" and the same should be returned. You can check the storage with:

./alphanet.sh client get storage for appendString

Try to run the transaction a few times or change the input and see what happens!


Storage fees

Every time we call the contract with a string that isn’t empty, the required storage increases. This isn’t without a cost – currently the cost for 1 byte of storage is 1 tez. So by running the command with "test1" as input, the contract has to spend 5 tez on storage fees. This means the contract will run out of money eventually, especially if it allows incoming transactions that don’t contribute any money. Note that on the alphanet the storage fee per byte is set to 0 tez for now.

How could we solve this issue?

One of the possibilities is to require any incoming transaction to contribute tez to the contract, and refuse those that don’t. How much the transaction should contribute depends on the length of the input, but Michelson doesn’t have an easy way to get it. The string type in Michelson is actually an array of up to 1024 bytes, so we could just ask everyone for 1024 tez.

code {PUSH tez "1024.00";
      AMOUNT;
      CMPLT;
      IF{FAIL}
        {DUP;
         DIP{CDR};
         CAR;
         SWAP;
         CONCAT;
         DUP;
         PAIR}}

CMPLT is syntax sugar for COMPARE and LT (less than), and lets us compare the amount of tez in the incoming transaction (AMOUNT) with the 1024 tez we are asking for using the contract. In case the transaction contains less than 1024 tez, we use the FAIL instruction to stop executing contract code and exit (this means the transaction won’t take place and any tez will stay where it was). Otherwise we execute the contract normally.

We can use this idiom to charge for using our contract in other contexts as well, whether we incur any storage fees or not. We can even refuse undesirable transactions in a similar manner when e.g. a caller fails to authenticate itself.

Although the storage fees will likely be adjusted before mainnet launch, using smart contracts to publish data this way is most likely going to be prohibitively expensive in practice. Luckily, most of the data that we might use in a contract is of fixed length (such as addresses or hashes). If a larger piece of data is required, it can be stored off the chain and a fixed length hash can be stored on the blockchain to prove authenticity.


Interacting with other contracts

In many cases, we will want our contract to call other contracts either to get some piece of trusted information or to make our code more modular (in the same way we split code into functions in other languages). To call another contract, we’ll need to instruct our code to send a transaction. We’ll need to specify the amount of tez to send, the input parameter and the address of the external contract. The input parameter type has to match the parameter type the external contract expects.

For example, let’s write a simple contract that takes a string, calls our appendString contract, and stores the result. Try to write it yourself, or look at the end of this section to see the code. We can use storage to specify the target contract, and hold the result at the same time like so:

storage (pair string (contract string string));
              ^       ^
              |       |
         result       address of the target contract

When originating the contract, we will use the initial storage to specify the address of our appendString contract like so:

./alphanet.sh client originate contract coolName for my_identity transferring 100 from money running container:call.tz -init '(Pair "placeholder" "TZ1X4ukutiNUCNrEcBFAXghjpgubL8t5j4Zq")'
                                                                                                                                     ^             ^
                                                                                                                                     |             |
                                                                                                        result will be here eventually             address of appendString goes here

To call the other contract, we’ll use the TRANSFER_TOKENS instruction and we’ll need to have these things on the stack:

            [ string : tez : (contract string string) ]
              ^        ^      ^
              |        |      |
          input     amount    address of the target contract

Since the contract’s type is (contract string string) both the input parameter and the return value need to be a string.

Furthermore, we’ll need to have the current contract’s storage below those, so altogether it will look like this:

[ string : tez : (contract string string) : pair string (contract string string) ]

This is needed e.g. when a complex contract requires inputs from multiple contracts, the temporary result needs to be stored before handing off control to another contract.

After the contract has been executed, we will have a return value of type string on top of the stack.

[ string : pair string (contract string string) ]

We can call the contract as usual. It will execute its code – call the contract specified by its storage, and save the result of that call into the other part of the storage:

./alphanet client transfer 0 from money to coolName -arg '"knock knock"'

Check the result:

./alphanet client get storage for coolName

My code for this contract is here:

parameter string;
return string;
storage (pair string (contract string string));
code {DUP;
      CAR;
      DIP{CDR;
          DUP;
          CDR;
          PUSH tez "0.00"};
      TRANSFER_TOKENS;
      DIP{CDR};
      DUP;
      DIP{PAIR};
      PAIR};


Better tools

By now, you might be thinking that writing contracts in Tezos is very cumbersome and annoying since you have to keep the whole stack in your head while writing a contract. To make things more convenient, you can use the michelson mode for emacs. It provides a stack visualization and can be seen in action in Tezos Today Ep. 2. I’m not aware of any plugins for other editors, so if you aren’t an emacs user you are out of luck at least for now.

Besides editor plugins, other projects with potential to improve the developer story of Michelson are compilers from other languages. There are a few of them already, like liquidity, which is a functional language with OCaml syntax that compiles to Michelson (and back). Other languages that compile to Michelson include fi and Juvix, a Haskell to Michelson compiler. These or similar newly created ones are likely to be used for writing more complicated contracts in the future.


Oracles

When writing contracts, we would often like to interact with real world systems, that have nothing to do with the blockchain. Contracts can only call other contracts, however, so the next best solution is to have a trusted entity that takes some off-chain information and makes it available as a contract on the chain. We call it an oracle – we have to trust anything it says, because we have no way of knowing what’s really happening off-chain from inside a contract, or what happened off-chain in the past. In contrast, we can easily verify any on-chain events since blockchain creation.

This approach still has some unresolved issues with incentivizing truthful reporting, as is discussed e.g. in the Blockchain: The Oracle Problems talk by Paul Sztorc.

We’ve covered originating contracts on the blockchain, triggering their execution by sending transactions to them and some of the caveats of using storage. Since interaction with off-chain systems is required for most interesting use-cases of smart contracts, we will look at writing a simple oracle in the next article.

Tezos applications: automated insurance

January 12, 2018
cryptocurrency tezos michelson blockchain smart contracts applications oracles golang

Demo: automated insurance

January 12, 2018
demo cryptocurrency tezos michelson blockchain smart contracts applications oracles golang react web

Getting started with Tezos #4: more complex oracle

November 17, 2017
cryptocurrency tezos michelson blockchain smart contracts oracles golang