Hackernoon logoHow to Use the Michelson Programming Language to Write Smart Contracts on Tezos by@claudebarde

How to Use the Michelson Programming Language to Write Smart Contracts on Tezos

Author profile picture

@claudebardeClaude Barde

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!

The stack

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:
PUSH value-type value
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 ;
code {
  ...
}
In addition to this structure, there are two rules you must keep in mind when writing a smart contract in Michelson:
  1. A pair containing the parameter and the storage 
    (pair parameter storage) 
    is always automatically pushed to the stack when the code is executed. Remember — if there’s no parameter, 
    Unit
     is used instead.
  2. The code must always return a pair containing a list of operations and the (updated) storage 
    (pair list(operation) storage)
    . The execution will stop when this kind of pair is the last thing remaining in the stack.

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:
  1. parameter unit
     indicates that the passed parameter is of type 
    unit
     (basically, no parameter).
  2. storage string
     indicates that the contract has storage of type 
    string
    .
  3. DROP
     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.
  4. PUSH
     brings a value on top of the stack, here the string “Hello world”.
  5. NIL
     is an opcode that adds an empty list of the specified type (
    operation
     here) on top of the stack.
  6. PAIR
     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: 
ADD
. 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:
parameter unit
: Once again, we are not using any parameter, so we are passing a unit.
storage int
: This time, we are saving a value of type integer into the storage.
DROP
: 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. 
int 2
 is going to be at the bottom of the stack once you push 
int 3
. 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.
ADD
 works on the same principle as 
PAIR
. 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. 
ADD
 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).
NIL
: As in the previous contract, we push an empty list of operations.
PAIR
: Creates the pair containing the list of operations and the new storage we need to stop the execution of the contract.

Conclusion

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.
Stay tuned!

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!