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.
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 Type | Description |
---|---|
string | For strings |
nat | For natural numbers |
int | For integers |
bytes | For bytes |
bool | For booleans, can be True or False |
unit | Placeholder 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.
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 Type | Description |
---|---|
operation | An internal operation emitted by a contract |
key | A public cryptography key |
key_hash | The hash of a public cryptography key |
address | An untyped contract address |
timestamp | Dates in real world |
mutez | 64 bit signed integers |
contract t | A contract |
signature | A cryptographic signature |
lambda t u | A lambda with arguments of type t and return type of u |
For a closer look at domain-specific data types, go here.
Stack operations
Operation | Description |
---|---|
DROP | Drop the top element of the stack |
DUP | Duplicate the top element of the stack |
SWAP | Exchange the top two elements of the stack |
PUSH t x | Push a value of x of type t onto the stack |
UNIT | Push a unit value onto the stack |
LAMBDA t u | Push a lambda onto the stack |
CAR | Push the left part of a pair onto stack |
CDR | Push the right part of a pair onto stack |
NIL (t) | Push an empty list of type ((list (t)) |
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.
Operation | Description |
---|---|
EQ | Checks if the top element of the stack is 0 |
NEQ | Checks if the top element of the stack is not 0 |
LT | Checks if the top element of the stack is negative |
GT | Checks if the top element of the stack is positive |
LE | Checks if the top element of the stack is negative or 0 |
GE | Checks if the top element of the stack is positive or 0 |
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
Operation | Description |
---|---|
CONCAT | String concatenation |
SIZE | number of characters in a string |
SLICE | String access |
COMPARE | Lexicographic comparison |
More on operations on strings? Check out: https://tezos.gitlab.io/008/michelson.html#operations-on-strings.
A smart contract to certify students
Let us 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 hangzhounet.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 };
You must understand the stack. Working with Michelson means working with a stack. What do we do in this part of the code? Let us take a look at the stack:
$ ./hangzhounet.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 us 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 us run test smart contract:
$ ./hangzhounet.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 us 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 us do a typecheck:
$ ./hangzhounet.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.
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 us test this contract:
$ ./hangzhounet.sh client run script container:certify.tz on storage 'Pair {} "tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN"' and input '"tz1efDUcgRaD6WcChETnhEqW57JMxRCree8a"' --source tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN
And let us test it with another --source
which is not in the storage 'Pair {} "tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN"'
:
$ ./hangzhounet.sh client run script container:certify.tz on storage 'Pair {} "tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN"' and input '"tz1efDUcgRaD6WcChETnhEqW57JMxRCree8a"' --source tz1efDUcgRaD6WcChETnhEqW57JMxRCree8a
This time it should fail. We can optimize 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 us deploy this contract. Therefore, first check the address of your accounts/contracts with:
$ ./hangzhounet.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:
$ ./hangzhounet.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.
For a closer look at the full grammar of Michelson, go here.
- Barde, Claude (2020): An Introduction to Michelson: The Scripting Language of Tezos (Part 1)
- Barde, Claude (2020): An Introduction to Michelson: The Scripting Language of Tezos (Part 2)
- Barde, Claude (2020): An Introduction to Michelson: The Scripting Language of Tezos (Part 3)
- camlCase: Michelson Tutorial Series
- Canou, Benjamin: Michelson presentation
- Michelson: The Language of Smart Contracts in Tezos
- Tezos Developer Documentation
- Tezos: Michelson Reference - Introduction, Types, Instructions