How to Use the Michelson Programming Language to Write Smart Contracts on Tezos
Tezos smart contract developer in the making 🌮
This is (Part 1): An Introduction to Michelson: the Scripting Language of Tezos
Michelson must be one of the most exciting programming languages for smart contracts at the moment. It’s a stack-based, strictly typed language in which smart contracts are written to ensure the safety of the Tezos blockchain. Michelson is comparable to the bytecode of the Ethereum smart contracts but it’s more readable, safer, and more robust. All the high-level languages you can use to write smart contracts for Tezos — like SmartPy, Ligo, or Lorentz — eventually compile down to Michelson.
In this first article, we will dip our toes into Michelson language, understand what “stack-based” means and write some very simple smart contracts. This article is mainly written for beginners in programming and/or Tezos development, but intermediate programmers who want to know more about Michelson will also find useful information here. We are going to use the Jupyter kernel developed by Baking Bad
to write Michelson code in the Jupyter notebook. You will find a link in each section if you want to see the code at work.
Let’s write some code!
To understand how Michelson works, one of the main concepts to understand properly is the stack. Every Michelson contract is a list of instructions that follow each other. These instructions are in a precise order and are executed in the order they’re written in.
Every instruction will manipulate the stack in some way. Imagine it as a pile of data. The instructions you write will cause an effect on the data present in the pile. You can, for example, add together two pieces of data on top of the pile, remove the one on the top, put another piece of data on top, transfer some tokens, etc. The stack works on a last in, first out basis: if you want to access a piece of data that is not at the top of the stack, you must first deal with the ones above it.
There are three main concepts you must remember when coding in Michelson:
- New data goes on top of the stack.
- The data in the stack only become accessible when they are at the top of the stack (or in the second position for some operations, as described below).
- The order in which the data are processed goes from the top of the stack to the bottom.
Let’s look at an example.
The PUSH Operation
If you want to add a piece of data on top of the stack, you will call the PUSH operation. This is how it works:
Note that there may already be data in the stack, in which case the new value will be put on top of them. This is how you push new data in Michelson:
For example, if you want to push an integer, you will write
PUSH int 2
, for a string, you will write
PUSH string "Tezos"
The Michelson Smart Contract Structure
A smart contract in Michelson displays a simple structure made of three components:
- The type of the expected parameter.
- The type of the storage.
- The Michelson code.
This translates to the following code:
parameter parameter-type ;
storage storage-type ;
In addition to this structure, there are two rules you must keep in mind when writing a smart contract in Michelson:
- A pair containing the parameter and the storage
is always automatically pushed to the stack when the code is executed. Remember — if there’s no parameter,
(pair parameter storage)
is used instead.
- The code must always return a pair containing a list of operations and the (updated) storage
. The execution will stop when this kind of pair is the last thing remaining in the stack.
(pair list(operation) storage)
A Simple Michelson Smart Contract
Now that we know about PUSH and the structure of a smart contract in Michelson, let’s write one!
For this contract, we are going to write a “Hello world” contract and save a string into the storage:
Here’s what happens when this code is executed:
indicates that the passed parameter is of type
(basically, no parameter).
indicates that the contract has storage of type
is an operation code that removes whatever is at the top of the stack. Remember, we said earlier that a pair with the parameter and the storage is automatically included on top of the stack at the beginning, we are not going to use it, we can just drop it.
brings a value on top of the stack, here the string “Hello world”.
is an opcode that adds an empty list of the specified type (
here) on top of the stack.
takes the two elements on top of the stack, creates a new pair containing these two elements, and pushes back the pair on the stack.
Note: Every instruction ends with a semi-colon (it is optional for the last instruction though).
Adding Integers and Saving the Result
Let’s introduce a new operation:
. You probably guessed what it does — add two numerical values together.
Here’s a simple contract that demonstrates how it works:
Let’s go through each operation to understand what is happening inside the stack:
: Once again, we are not using any parameter, so we are passing a unit.
: This time, we are saving a value of type integer into the storage.
: We don’t need the initial pair, so we can get rid of it to make room for the values we will actually need.
PUSH int 2 ; PUSH int 3 ;
: Note that the order is crucial.
is going to be at the bottom of the stack once you push
. In the case of an addition, the order doesn’t matter too much, but if you subtract two numbers, for example, it’s essential to push them in the right order.
works on the same principle as
. You take the first two elements on top of the stack and get a single value out of them that you push back to the stack.
will add two numbers together. Note that the numbers have to be both of the same numerical type (you cannot, for example, add an integer and a nat together).
: As in the previous contract, we push an empty list of operations.
: Creates the pair containing the list of operations and the new storage we need to stop the execution of the contract.
The complexity of the Michelson language is often overestimated. That’s probably due to the fact that there are no beginner-friendly tutorials out there and the rare documentation available online is extremely technical and difficult to read for neophytes. This’s why I decided to go through the process of learning Michelson myself, using the difficult documentation to create a series of tutorials that I hope are more accessible.
Understanding Michelson is key to understanding and appreciating the uniqueness of the Tezos blockchain and what makes it more secure and more useful.
In the next part, we will continue diving into Michelson. We’ll write some simple smart contracts and explore the amazing Jupyter notebooks created by the Baking Bad team
that will allow us to write Michelson code and understand exactly what’s going on.
Subscribe to get your daily round-up of top tech stories!