B9lab Logo
Tezos Developer Portal
Developer PortalDeveloper Portal

Michelson - A Smart Contract Language

A general overview


We already talked about Michelson, and you have come in contact with it when you took a look at the compiled code. But we did not dive deeper, and just wrote a simple repeater contract.

tip icon

Before you dive deeper, please make sure to read the previous section on Michelson.

Here, we want to give a general overview. In the end, we want to write a pure Michelson code to have a more complex example of the language.

Core data types

Data TypeDescription
stringFor strings
natFor natural numbers
intFor integers
bytesFor bytes
boolFor booleans, can be True or False
unitPlaceholder with value Unit
list (t)An immutable list of type (t)
pair (l) (r)A pair of two values of type (l) and (r)
option (t)Optional value of type (t)
or (l) (r)A value holding a value of type (l) or (r)
set (t)An immutable set of type (t)
map (k) (t)An immutable map of type (k) to type (t)
big_map (k) (t)Lazily deserialized maps, limited use.

Notice, that types are lowercase in Michelson. A data constructor is capitalized(e.g. True or False) and instructions are uppercase.

tip icon

Do you want to take a closer look at core data types and notations, check this out: https://tezos.gitlab.io/008/michelson.html#core-data-types-and-notations.

Domain-specific data types

Data TypeDescription
operationAn internal operation emitted by a contract
keyA public cryptography key
key_hashThe hash of a public cryptography key
addressAn untyped contract address
timestampDates in real world
mutez64 bit signed integers
contract tA contract
signatureA cryptographic signature
lambda t uA lambda with arguments of type t and return type of u
tip icon

For a closer look at domain-specific data types, go here.

Stack operations

OperationDescription
DROPDrop the top element of the stack
DUPDuplicate the top element of the stack
SWAPExchange the top two elements of the stack
PUSH t xPush a value of x of type t onto the stack
UNITPush a unit value onto the stack
LAMBDA t uPush a lambda onto the stack
CARPush the left part of a pair onto stack
CDRPush the right part of a pair onto stack
NIL (t)Push an empty list of type ((list (t))
tip icon

For a more detailed account on stack operations, we recommend: https://tezos.gitlab.io/008/michelson.html#stack-operations.

Comparison

The COMPARE instruction works for each comparable type. The result will be 0 if the top two elements of the stack are equal, -1 if the first element in the stack is less than the second, and +1 otherwise.

OperationDescription
EQChecks if the top element of the stack is 0
NEQChecks if the top element of the stack is not 0
LTChecks if the top element of the stack is negative
GTChecks if the top element of the stack is positive
LEChecks if the top element of the stack is negative or 0
GEChecks if the top element of the stack is positive or 0
tip icon

For more information on generic comparison: https://tezos.gitlab.io/008/michelson.html#generic-comparison.

In addition, there are syntactic sugars for COMPARE.

Operations on strings

OperationDescription
CONCATString concatenation
SIZEnumber of characters in a string
SLICEString access
COMPARELexicographic comparison
info icon

More on operations on strings? Check out: https://tezos.gitlab.io/008/michelson.html#operations-on-strings.

A smart contract to certify students

Let's write a smart contract using this knowledge. We want to rewrite our certification smart contract with Michelson.

Create a new file certify.tz in the same folder as edonet.sh and paste:

parameter address;
storage (map address bool);

code { DUP;
       CDR @certified;
       SWAP;
       CAR @student; 
       DIP { PUSH bool True; SOME; };
       UPDATE;
       NIL operation;
       PAIR };

It is important that you understand the stack. Working with Michelson means to work with a stack. What do we do in this part of the code? Let's take a look at the stack:

$ ./edonet.sh client typecheck script container:certify.tz -details

This should give you the output:

{ parameter address ;
  storage (map address bool) ;
  code { /* [ pair (address @parameter) (map @storage address bool) ] */
         DUP
         /* [ pair (address @parameter) (map @storage address bool)
            : pair (address @parameter) (map @storage address bool) ] */ ;
         CDR @certified
         /* [ @certified map address bool
            : pair (address @parameter) (map @storage address bool) ] */ ;
         SWAP
         /* [ pair (address @parameter) (map @storage address bool)
            : @certified map address bool ] */ ;
         CAR @student
         /* [ @student address : @certified map address bool ] */ ;
         DIP { /* [ @certified map address bool ] */
               PUSH bool True
               /* [ bool : @certified map address bool ] */ ;
               SOME
               /* [ option bool : @certified map address bool ] */ }
         /* [ @student address : option bool : @certified map address bool ] */ ;
         UPDATE
         /* [ @certified map address bool ] */ ;
         NIL operation
         /* [ list operation : @certified map address bool ] */ ;
         PAIR
         /* [ pair (list operation) (map @certified address bool) ] */ } }

First, let's understand the syntax of the output. We see:

  code { /* [ pair (address @parameter) (map @storage address bool) ] */

This means if the program starts, the stack will have one element. The type of this element is pair (address @parameter) (map @storage address bool).

You can see that the output indicates the @parameter and @storage. In the next step we get the map of the certified students with:

DUP;
CDR @certified;

Here you see that we use @certified so the output tells us:

@certified map address bool

This is very useful for understanding the stack. We want to have a map address bool to store students certification status.

Now we can take the address of the student which we want to certify:

SWAP;
CAR @student; 

The result is:

/* [ @student address : @certified map address bool ] */ ;

So we have two elements in the stack, an element of type address and an element of type map address bool.

Now we want to update the map :

DIP { PUSH bool True; SOME; };
UPDATE;

DIP will keep the top of the stack so we can push a type of option bool between the student address and map to get the stack:

[ @student address : option bool : @certified map address bool ]

This is because of the UPDATE instruction. You can find it in the Michelson documentation:

UPDATE / x : Some y : {} : S  =>  { Elt x y } : S
UPDATE / x : Some y : { Elt k v ; <tl> } : S  =>  { Elt k y ; <tl> } : S

So this way we add a new student with the address and True to indicate that this student is certified. Now we need to match the type of the output:

NIL operation;
PAIR };

Let's run test smart contract:

$ ./edonet.sh client run script container:certify.tz on storage '{}' and input '"tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN"' --trace-stack

You can also deploy the contract and interact with it further. But as you can see, this contract will not check who is issuing a certificate.

Let's change this:

parameter address;
storage (pair (map address bool) address);

code { DUP;
       CDR;
       CDR @certifier;
       DUP;
       SENDER;
       COMPARE;
       NEQ ;
       IF { PUSH string "Only certifier is allowed to certify" ; FAILWITH }
          { UNIT } ;
       DROP;
       SWAP;

       DUP;
       CDR;
       CAR @certified;
       SWAP;
       CAR @student; 
       DIP { PUSH bool True; SOME; };
       UPDATE;
       PAIR;
       
       NIL operation;
       PAIR };

This time we have:

CDR;
CDR @certifier;
DUP;
SENDER;
COMPARE;
NEQ ;
IF { PUSH string "Only certifier is allowed to certify" ; FAILWITH }
  { UNIT } ;

at the beginning. SENDER will give us the address of the contract which is calling our contract. We can COMPARE it with the certifier in the storage. It will push a zero only if those addresses match. We check this with NEQ and if it is not zero, we will abort the transaction with FAILWITH.

Let's do a typecheck:

$ ./edonet.sh client typecheck script container:certify.tz -details

The output will be:

{ parameter address ;
  storage (pair (map address bool) address) ;
  code { /* [ pair (address @parameter) (pair @storage (map address bool) address) ] */
         DUP
         /* [ pair (address @parameter) (pair @storage (map address bool) address)
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         CDR
         /* [ @storage pair (map address bool) address
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         CDR @certifier
         /* [ @certifier address
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         DUP
         /* [ @certifier address : @certifier address
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         SENDER
         /* [ @sender address : @certifier address : @certifier address
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         COMPARE
         /* [ int : @certifier address
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         NEQ
         /* [ bool : @certifier address
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         IF { PUSH string
                   "Only certifier is allowed to certify"
              /* [ string : @certifier address
                 : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
              FAILWITH
              /* [] */ }
            { /* [ @certifier address
                 : pair (address @parameter) (pair @storage (map address bool) address) ] */
              UNIT
              /* [ unit : @certifier address
                 : pair (address @parameter) (pair @storage (map address bool) address) ] */ } ;
         DROP
         /* [ @certifier address
            : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
         SWAP
         /* [ pair (address @parameter) (pair @storage (map address bool) address)
            : @certifier address ] */ ;
         DUP
         /* [ pair (address @parameter) (pair @storage (map address bool) address)
            : pair (address @parameter) (pair @storage (map address bool) address)
            : @certifier address ] */ ;
         CDR
         /* [ @storage pair (map address bool) address
            : pair (address @parameter) (pair @storage (map address bool) address)
            : @certifier address ] */ ;
         CAR @certified
         /* [ @certified map address bool
            : pair (address @parameter) (pair @storage (map address bool) address)
            : @certifier address ] */ ;
         SWAP
         /* [ pair (address @parameter) (pair @storage (map address bool) address)
            : @certified map address bool : @certifier address ] */ ;
         CAR @student
         /* [ @student address : @certified map address bool : @certifier address ] */ ;
         DIP { /* [ @certified map address bool : @certifier address ] */
               PUSH bool True
               /* [ bool : @certified map address bool : @certifier address ] */ ;
               SOME
               /* [ option bool : @certified map address bool : @certifier address ] */ }
         /* [ @student address : option bool : @certified map address bool
            : @certifier address ] */ ;
         UPDATE
         /* [ @certified map address bool : @certifier address ] */ ;
         PAIR
         /* [ pair (map @certified address bool) (address @certifier) ] */ ;
         NIL operation
         /* [ list operation : pair (map @certified address bool) (address @certifier) ] */ ;
         PAIR
         /* [ pair (list operation) (pair (map @certified address bool) (address @certifier)) ] */ } }

Have a look at:

NEQ
/* [ bool : @certifier address
  : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
IF { PUSH string
         "Only certifier is allowed to certify"
    /* [ string : @certifier address
       : pair (address @parameter) (pair @storage (map address bool) address) ] */ ;
    FAILWITH
    /* [] */ }
  { /* [ @certifier address
       : pair (address @parameter) (pair @storage (map address bool) address) ] */
    UNIT
    /* [ unit : @certifier address
       : pair (address @parameter) (pair @storage (map address bool) address) ] */ } ;
DROP

So if the result of COMPARE is equal to zero, we will use UNIT to push a placeholder into the stack. Then we will DROP it. We do so because IF needs two arguments.

tip icon

You can also use {} as the second branch of IF, or put the rest of the code in the second branch, as we will do later.

Now, let's test this contract:

$ ./edonet.sh client run script container:certify.tz on storage 'Pair {} "tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN"' and input '"tz1efDUcgRaD6WcChETnhEqW57JMxRCree8a"' --source tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN

And let's test it with another --source which is not in the storage 'Pair {} "tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN"':

$ ./edonet.sh client run script container:certify.tz on storage 'Pair {} "tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN"' and input '"tz1efDUcgRaD6WcChETnhEqW57JMxRCree8a"' --source tz1efDUcgRaD6WcChETnhEqW57JMxRCree8a

This time it should fail. We can optimise our code and use the second branch of the IF and a big_map instead of a map:

parameter address;
storage (pair (big_map address bool) address);

code { DUP;
       CDR;
       CDR @certifier;
       DUP;
       SENDER;
       COMPARE;
       NEQ ;
       IF { PUSH string "Only certifier is allowed to certify" ; FAILWITH }
          { SWAP;
            DUP;
            CDR;
            CAR @certified;
            SWAP;
            CAR @student;
            DIP { PUSH bool True; SOME; };
            UPDATE;
            PAIR;
            NIL operation;
            PAIR 
            };
       };

Note that you can only have one big_map in a contract. This limitation of big_map will probably be dropped in the future.

Let's deploy this contract. Therefore, first check the address of your accounts/contracts with:

$ ./edonet.sh client list known addresses

Warning:
  
                 This is NOT the Tezos Mainnet.
  
               Do NOT use your fundraiser keys on this network.

faucetWallet: tz1fM3iHj7qWUrUStFwDuhDmjPeiQCWwBDDZ (unencrypted sk known)
myFirstKey: tz1dJbDNdzM5xFVSKkwt5NVPsdgQmWBQpfng (unencrypted sk known)

Then you can use one of your active accounts:

$ ./edonet.sh client originate contract certify for faucetWallet transferring 0 from faucetWallet running container:certify.tz --init 'Pair {} "tz1fM3iHj7qWUrUStFwDuhDmjPeiQCWwBDDZ"' --burn-cap 0.5

You will receive the contract address if it is deployed:

New contract KT1MC1dAuJxNcpNu8KMZP3F5RbjryDJ2dF35 originated.
The operation has only been included 0 blocks ago.

You can check its state and script with Better Call Dev.

tip icon

For a closer look at the full grammar of Michelson, go here.

reading icon
Discuss on Slack