on-chain governance.
Tezos is using a Proof Of Stake (PoS) consensus algorithm which is consuming less electricity than Proof of Work algorithm. Improvement of consensus algorithm has been a key feature for the last 6 years in the world of blockchains (NXT (2013), Ethereum is working on PoS for the last years, and many other new blockchains are heading toward PoS).
Tezos uses a Virtual Machine that interprets Michelson language. This language is a low-level assembly-like stack-based language which has the particularity to allow formal proof. Ensuring community of the proper-functioning scripts might be a key element to create trust.
Because low-level languages are harder to read and maintain, other new high-level languages and tools are being developed. The choice of this low-level is to provide the formal verification of the execution of the smart contract. This is a key feature that notably the financial sector is keen to have.
An overview can be found on the Tezos developer portal and in this article we will describe some of them.
Link : https://tezos.gitlab.io/whitedoc/michelson.html
Who : Nomadic labs
To develop smart contracts, the main language is Michelson, a stack-based language.
https://gist.github.com/frankhillard/00eb5b2428901a66b26123a7a99c7558
Code available on GitHub : https://github.com/vnea/michelson-samples/blob/master/src/contracts/counter.tz
In a Michelson code there are 3 sections :
As you can see, in the “code” section, it is not possible to define different functions like in Solidity but you you can define what is called an entrypoint with annotations (%increaseCounterBy et %decreaseCounterBy) in the above example. And then in the code, with the IF_LEFT Michelson instruction, we can do the implementation of each entrypoint/function.
If you want to learn Michelson language, a tutorial is available here and a lot of example are available here.
It is mandatory to do a check after each division (to prevent division per 0), after trying to get an element of a map (to avoid “null pointer exception”, there is no, implicit casts, no overflow, etc.)
Example of getting an element of a map
https://gist.github.com/frankhillard/d8c88957272ba5150a65efcd7264f6fd
Each time a new Core tezos protocol is released with new Michelson instructions, it is possible to use them. With a high level language, we actually have to wait for an implementation to cover these new instructions.
In the long term, we don’t see this as a key differentiator for the business.
One of the main feature of tezos is to use Michelson language for implementing smart contracts. This choice of language is due to the ability of making a formal verification of a smart contract code.
Some tools and language have been developed by the Caml community (INRIA, CNRS, …) in order to make formal verification. One of them is Coq, a proof assistant using Gallina language.
Nomadic labs is working on Mi-Cho-Coq which is the specification of Michelson in Coq (a proof assistant using Gallina language). So, Mi-Cho-Coq is a Coq framework for verifying the functional correctness of Michelson smart contracts. Mi-Cho-Coq allows users to produce a proof of their smart contract (written in Michelson).
Each time an instruction is run, it never modify an existing element of the stack : it can only add or remove elements. It avoid side effects like having an invalid state of a data : the data is defined once in a valid state, and we know that it will not change to an invalid state during the whole execution.
As it is a stack-based language, whenever you have to do some computations with a data in the parameter or the storage, you have to use some instructions to navigate through the stack to get this specific data. It sometime be tricky. There is an online tool called Try Michelson which allows to see the evolution of the stack. There is also a plugin for IntelliJ and one for Emacs if you prefer to have a local environnement.
Michelson stack view with variable annotations
As there is no variable, it is not possible to name things. So if a new developer starts to work an existing Michelson code and he has no context about the business, it will take a lot of time for him to understand the logic.
If we take the same counter contract but written in Solidity, in 20 seconds we can understand the purpose of the contract.
https://gist.github.com/frankhillard/f12fd7b14989336b84438e64469912dd
In Michelson, without comments and annotations, it will take more time to understand :
https://gist.github.com/frankhillard/8d9df3598c09853d571aa6e39710e9e1
So here it is just a simple contract. But imagine how complex it can be with a larger contract.
Hard to read the code implies hard to maintain it. One important thing to understand, is at the end of the code, the top element on the stack must be a pair of list of operations (~= transactions in Ethereum) and the storage. So if you want to add new properties/attributes in the structure of the storage, almost all the code must be updated as Michelson instructions depend on what is on the stack. For example, if on the top of the stack there is an integer which is the storage and the next instruction is GT (=checks that the top element of the stack is greater than zero), if the storage is changed to a pair of a string and an int, the code needs to be updated to split this pair and only keep the integer. So defining the structure of the storage at the beginning is a good practise. The same goes for the parameter.
Let’s get the previous contract example and let’s keep the previous counter after each update.
In Solidity, it is simple :
https://gist.github.com/frankhillard/a37905470bbb789dbe7eb514a6b3091c
Before seeing the solution, you can try by yourself to update the Michelson code.
Here the solution In Michelson :
https://gist.github.com/frankhillard/361a791d66ce29cda930c59c7fa46f82
We can see that if we read the Michelson code, if a bit harder to directly understand the new feature.
For information, it is a good practise to use the variable annotation @, it will be easier to understand what is in the stack :
Michelson stack view with variable annotations
From a contract, it is not possible to have an entrypoint which returns a data (after a computation or from the storage) outside the contract. So if you have a contract A calling a contract B, B cannot return any value to contract A. There is a pattern called continuation passing style where contract A calls contract B by passing its address and then contract B can call contract A and it gives its storage. But it can cost a lot of gas.
GitLab : https://gitlab.com/nomadic-labs
Mail : contact@nomadic-labs.com
Slack : https://tezos-dev.slack.com (need an invitation)
Link : https://ligolang.org/
Who : Stove Labs
LIGO language is a functional programming language based on Pascal/OCaml to write smart contracts. The LIGO compiler produces Michelson code from a LIGO code.
https://gist.github.com/frankhillard/b5ae7464a0b2cfe4363ced7bc9c9f493
And here is how one’s can invoke the contract :
ligo dry-run counter.ligo main Increment(5) 0
The ligo tutorial example taco-shop is available here. It focuses on how to use a map as storage and how to read/modify the storage. This following command line shows you how to initialize a map storage.
https://gist.github.com/frankhillard/935d3207fe353036cc3e80dd26d4ecca
This command line (above) simulates execution of the smart contract entrypoint. It shows how one’s can specify the initial storage state.
It is easier to manipulate complex data structure (records containing maps).
LIGO implements a way to call contract from another contract which means it is possible to implement some casual patterns (such as proxy contract, delegation, continuation passing style (callbacks)).
As the tool is young, the dev team quickly answers when have some issues (on Slack).
LIGO is based on Pascal/OCaml which implies that it is a strong typed language :
It is possible to split implementation into multiple files (#include instruction). It can be nice if we want to define the types in a different file for example.
An online IDE for testing a single contract is available and allow to avoid local installation.
Lack of maturity concerning error message. Sometimes error line is not mentioned, nor in which function the problem is located.
For example, if you try to compile this code, only “not an option” will be mentioned :
var addr: address := None;
LIGO code with not helpful error message
The compiler has an issue when generating michelson with statements like get_contract/transaction (returns an option but expect contract). This error can be fixed manually inside the Michelson generated source code by adding an ASSERT_SOME or IF_SOME Michelson instruction.
In the future, the strategy is to also provide a framework allowing to verify the functional correctness of smart contract written in Ligo. (This framework is not yet available).
There is :
Discord : https://discord.gg/9rhYaEt (all messages of Slack are re-sent here by a bot)
Slack : https://tezos-dev.slack.com/archives/CFX0B8Q3X (need an invitation)
Link : http://smartpy.io
SmartPy : François Maurel and Roland Zumkeller
Warning :
A new version has been released the 11th November : http://smartpy.io/babylonTest/
It has not tested so maybe the lacking features are now implemented is this version.
SmartPy allows to develop smart contracts in Python and then compile then into Michelson.
https://gist.github.com/frankhillard/1c1206587e962f4a9f0284d2ac21a5ec
It is an easy language to learn and it exists since a long time so it will not be difficult for a new developer to get into SmartPy.
If you need to test a piece of code quickly, it is not needed to setup a whole environment on the computer.
The SmartPy compiler generates explicit error messages. Those error messages are easily understandable and thus provide a good help for debugging smart contracts. For example, here is a message error saying there is a missing parameter : “Error: Type error, non matching names [y] and [z] in records {x: nat, y: intOrNat} and {x: nat, z: nat} Bad params type in line 51”.
It can be confusing to mentioned this type of thing but it is a new language.
You can find some patterns and functions that are not specified in the documentation so it is a good idea to check all the example before starting to develop.
Lots of examples are provided by the SmartPy IDE which allow users to load/test template contracts. Click on Menu then Load templates :
SmartPy examples
It is possible to write unit tests to test the SmartPy code. As you can see in the previous snippet of code, the end of the snippet of code contains tests wrapped as a scenario. The scenario simulate invocation of contract entrypoints and verify that the storage has been modified accordingly.
The inheritance between contracts is applied only to functions but not to class attributes. Once the mother contract has defined a storage structure there is no overriding of the storage structure by the child contract (storage is not shared).
It is not mentioned how to defined a embedded of map : if you try it will generate Michelson with error messages inside it (a tip to do it is given further in the article).
In an entrypoint function, it is not said that it can only have one argument. If need to pass multiple parameters.
```
@sp.entryPoint
def add(self, firstValue, secondValue):
self.data.value = firstValue + secondValue
```
Do not work
Solution :
```
@sp.entryPoint
def add(self, params):
self.data.value = params.firstValue + params.secondValue
```
Works.
The documentation does not specify that it is possible to define the type of a map like this :
```
sp.bigMap(tkey = sp.TAddress, tvalue = TInt) // If TInt does not work, use TSimple(“int”)
```
In SmartPy, a Map type has a limit of number of entry that can be stored. Above 10 elements in the map, the SmartPy compiler will expect a big_map type instead of map.
It can also reach the size limit if the structure used in the map is a record with fields, or multiple embedded maps.
The SmartPy language does not support the transaction instruction which allows a contract to send a transaction to another contract.
Warning
This feature is now normally supported in the new version released in 11th november.
even if it is possible to write “tests” in SmartPy, implementation and tests has to be put in the same file. As a developer, we usually prefer to split them in different files. Even if there are several contracts, in the case you want to use inheritance, all the contracts have to be put in the same file. When searching for something, it is then more difficult to find the information in a large file.
When downloading SmartPy or using the online editor, it is very hard to know what can be used and what has been fixed. And we are only inform about new versions on Telegram or Tweeter.
There is no plan for providing a framework allowing to verify the functional correctness of smart contract written in SmartPy.
When using embedded maps , the SmartPy compiler may not be able to deduce the type of key and values of inner maps. A setType function can be useful to specify the types used in maps and embedded maps. In IDE , it displays a warning when some types cannot be deduced. Actually the generated michelson code might be invalid when warnings occur.
When evaluating a boolean condition , use the “~” operator for negation.
Telegram : https://t.me/SmartPy_io
Twitter : https://twitter.com/SmartPy_io
Link : https://github.com/baking-bad/pytezos
Who : Baking Bad
PyTezos is a Python SDK for Tezos: RPC, cryptography, operations, smart contract interaction. There are also other SDK like ConseilJS, Taquito, etc.
But one thing interesting is the ability to test Michelson code :
https://gist.github.com/frankhillard/2be0380bebd5fad874dd099309b37691
https://github.com/vnea/michelson-samples/blob/master/src/tests/test_counter.py
Whatever the high level language chosen, it will produce Michelson. So if the high level language does not provides a testing framework, the tests can be written with PyTezos and then run with Pytest.
No need to set up a node to run the test PyTezos has their own public node (with the last protocol Babylon), when the test is run, the data (Michelson code + test data) are sent to this node and then the code will just be interpreted. The contract is not deployed so it is really fast. It is then possible to put it in a CI. Of course, it is possible use another node. Under the hood, it is the run_code RPC call which executes the code.
As the tool is still young, there can still be some issues that can slow down your development. Baking Bad is quite reactive (just open an issue on GitHub).
It is possible write a test which expects for an exception to be thrown. It can happen in the case you want to test some preconditions, like if the given value is positive, if the caller is the administrator, etc.
From the counter example, we did another counter contract with an additional check and here it is an example on how you can write the expect exception in a test :
https://gist.github.com/frankhillard/e37818167c9e33090387bc214ae8be2e
https://github.com/vnea/dev-smart-contracts-tezos/blob/master/michelson/tests/test_counter_with_check.py
There is an article about how to write tests with PyTezos but it is not complete. It only shows a simple case where the contract has only one element in the storage. But when the storage is more complex, or if there is no annotation for the storage/entrypoints in the Michelson code, the syntax will not be exactly the same. Without annotations, you will have to pass an array and with annotations, you will have to pass a JSON object.
As it is not written in the official documentation or in the article, you have to look for the examples.
Sometimes, we lost a lot of time because depending on the value passed to an entrypoint, an error can be thrown for no reason. For example, if we look again at the test test_increaseCounterBy_should_increase_counter_in_storage_by_increase_value and if we change the increase_value from 5 to 0, an error without any sense will be raised.
GitHub : https://github.com/baking-bad/pytezos
Telegram : https://t.me/baking_bad_chat
Twitter: https://twitter.com/tezosbulletin
Tezos provides tools for synchronizing (running a node of the network) and interacting with the blockchain (via RPC calls or smart contract invocation). Among these tools we must mention :
All information about Tezos node /client are available here
GitLab : https://gitlab.com/nomadic-labs
Mail : contact@nomadic-labs.com
Slack : https://tezos-dev.slack.com (need an invitation)
Link : https://stove-labs.github.io/granary
Who : Stove labs
Granary provides a comprehensive set of tools that you can use to develop Smart Contracts and Dapps on the Tezos platform. It provides a local sandboxed node where you have full control over the running blockchain, with block explorers and other supportive tools pre-configured & ready to use.
It automatically registers an account with some tez, and inject the protocol in the local sandboxed node with just some command line, the node run in docker container.
‘’’
granary client - list known contracts
‘’’
Granary provides commands for initializing/starting/restarting a tezos-node.
Please refer to granary documentation (here).
For the moment, it is not possible to inject the Babylon protocol, which is the last one.
User permission problem when starting the node (must change permissions with following commands):
When invoking contract entrypoints, string parameters must be escaped (call parameters and storage).
Github : https://github.com/stove-labs/granary
Link : https://tplus.dev
Who : TulipTools
Tplus is a all in one tool that helps you manage Tezos environments (sandboxed and public nodes) for use for development on top of Tezos. It is a all in one tool :
Twitter : https://twitter.com/TulipToolsOU
To sum up, if you are starting to implement a Dapp for Tezos , it is very relevant to ask yourself which language should be used and how to test and deploy.
You have multiple choices : Michelson, LIGO, SmartPy, Archetype, Morley / Lorentz (write smart contract in Haskell). We did not work with the last two languages so we can’t give you information about them but they seem very promising.
First, we won’t recommend to write smart contracts in Michelson for few reasons :
So using a high level language would save time when implementing incrementally your smart contract and provide source code that can be read and understood by more people, thus creating trust on the DApp. Now choosing between SmartPy and LIGO depends on the architecture of contracts that need to be deployed.
Feature | SmartPy | LIGO |
Many contracts in single file | ok | ko |
Include other files | ko | ok |
Need to call another contract | ko (ok since 11th november) | ok |
Inheritance / overriding functions | Partially ok | ko |
Complex data structure | ok | ok |
Use of big map | only 1 | multiple |
formal verification framework | None | mi-cho-coq for ligo (coming soon) |
Note : we only recommend to use Michelson if you need an instruction that is not supported yet by a high level language. It can happen when a new protocol introduces new Michelson instructions.
With LIGO, there is no testing framework (yet ?).
With SmartPy, there is a testing framework but it is not perfect.
You have to options to write tests :
In both cases, it is not possible to write a test which expects a error. For example if an entrypoint but you expect that only the owner of the contract can call it, you would want to write a test to cover this case but it is not possible.
As LIGO does not have a testing framework and as SmartPy testing framework has some drawbacks, we suggest you to use PyTezos to write the unit tests. It will directly tests the Michelson code (interpreted by the public node of PyTezos) and it is also possible to write a test expecting an error (like the case describe above) even if it is not perfect. And of course, the tests can be run in a CI.
Once the michelson has been generated (by ligo or SmartPy) it can be tested on a real running tezos-node.
We recommend to use PyTezos for testing your Smart contract
We recommend to use directly tezos-node if you are about to deploy on Babylonnet.
Since Granary does not support yet the Babylon protocol activation, it can be used on older protocols (alphanet, …).