Tezos ecosystem - October 2019
[DISCLAIMER: This snapshot has been made in October 2019. The ecosystem is really moving a lot therefore everything can change very quickly. Moreover, few versioning principles are really used so we can’t list the stack that we used for our study]
This article is a technological deep-dive to the Tezos world, with a holistic presentation of how to develop Dapps on this promising blockchain.
We saw that few articles provide a extended insight of all the tooling. At OCTO, we aim to provide to the community the understanding of all the high-level languages, testing frameworks and toolings : Tezos CLI, Michelson, LIGO, SmartPy, etc.
This article describes the status of all these technologies at the end of october 2019. The community of developers is growing and working hard to improve the ecosystem of Tezos (testing frameworks and compilers for high-level languages) and many existing issues might be solved while your reading this.
As a reminder, Tezos is a blockchain technology (meaning a decentralized network) with ability to deploy and execute code over this ledger. The related white paper is available here.
This article is a follow-up of a (french version) that introduce the on-chain governance.
I - Overview of the core blockchain protocol
Consensus
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).
Language
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.
High-level languages and tools
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.
II - Languages
Michelson (low-level)
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 :
- storage : like in Solidity (for Ethereum), it contains the data of the contract.
- parameter : the external information/data to pass to the contract.
- code : it contains the logic of the contract, it can manipulate the parameter and the storage, do computations and then update the storage.
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.
The most (+)
Strong typed language
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
Language at the core of the evolution of the protocol
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.
Formal verification
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).
Immutability
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.
The least (-)
No variable
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
Hard to read the code
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 maintain
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
No getter
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.
Contact
GitLab : https://gitlab.com/nomadic-labs
Mail : contact@nomadic-labs.com
Slack : https://tezos-dev.slack.com (need an invitation)
LIGO (high-level)
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.
The most (+)
Assignments of embedded maps has been fixed recently
It is easier to manipulate complex data structure (records containing maps).
Communication between contracts
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)).
Good reactivity
As the tool is young, the dev team quickly answers when have some issues (on Slack).
Strong types language
LIGO is based on Pascal/OCaml which implies that it is a strong typed language :
- prevent bugs and untreated cases or invalid type casting.
- case instruction is very user friendly feature.
Interesting features
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.
The least (-)
Error message
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
Invalid Michelson code generated sometimes
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.
Formal verification
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).
Lack of documentation
There is :
- no mention of big_map.
- no enough tips and tricks / work around :
- nat - nat => int but can be recasted to nat using abs LIGO function.
- how to fix LIGO compiler bugs (for example, add ASSERT_SOME or IF_SOME instruction before contract instruction).
- no release notes : it is difficult to know what is currently working or not and what has been fixed.
Contact
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)
SmartPy (high-level)
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
The most (+)
Python
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.
Online editor
If you need to test a piece of code quickly, it is not needed to setup a whole environment on the computer.
Error messages
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.
Examples
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
The least (-)
Testing framework
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.
Inheritance
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).
Lack of/incoherent documentation
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”)
```
One BigMap per contract
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.
Cannot call another contract
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.
Everything in one file
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.
No changelog
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.
Formal verification
There is no plan for providing a framework allowing to verify the functional correctness of smart contract written in SmartPy.
Tips & Tricks
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.
Contact
Telegram : https://t.me/SmartPy_io
Twitter : https://twitter.com/SmartPy_io
III - Testing
PyTezos
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
The most (+)
Test the Michelson
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.
Reactivity
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).
Handling exceptions
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
The least (-)
Lack of documentation
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.
Unexpected bugs
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.
Contact
GitHub : https://github.com/baking-bad/pytezos
Telegram : https://t.me/baking_bad_chat
Twitter: https://twitter.com/tezosbulletin
IV. Infrastructure
Tezos CLI
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 :
- tezos-node: the tezos daemon itself which is running the tezos node;
- tezos-client: a command-line client allowing users to interact with blockchain.
- listing of registered account or deployed contracts
- monitoring peer to peer layer / connect nodes
- list of rpc entrypoint;
- rpc call for inspecting genesis block
- rpc call for checking node status
- rpc call for listing protocols
- (full reference is available here: https://tezos.gitlab.io/tezos/api/rpc.html)
- tezos-admin-client: a command-line administration tool for the node;
- tezos-alpha-baker: Among the peer to peer network some nodes are responsible for baking transactions. This tool contains a client (for command line purposes) and a daemon to bake on the Tezos network;
- tezos-protocol-compiler: a protocol compiler used for developing new version of the economic protocol.
All information about Tezos node /client are available here
Contact
GitLab : https://gitlab.com/nomadic-labs
Mail : contact@nomadic-labs.com
Slack : https://tezos-dev.slack.com (need an invitation)
Granary
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.
The most (+)
Easy installation / setup of environment
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.
Easy use of command line instructions: very similar to tezos client CLI.
‘’’
granary client - list known contracts
‘’’
Uses a docker image for running the tezos node in sandbox mode
Granary provides commands for initializing/starting/restarting a tezos-node.
Please refer to granary documentation (here).
The least (-)
Babylon protocol
For the moment, it is not possible to inject the Babylon protocol, which is the last one.
Lack of documentation
User permission problem when starting the node (must change permissions with following commands):
- chmod -R 777 my-granary-project
- chmod 777 .granary
When invoking contract entrypoints, string parameters must be escaped (call parameters and storage).
Contact
Github : https://github.com/stove-labs/granary
V - Additional tools
Tplus
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 :
- sandboxed local node with Babylon protocol.
- a Web interface with VScode with an access to a Terminal.
- be able to run all Tezos CLI command from this Terminal.
- LIGO and SmartPy CLI are also accessible from this Terminal.
Contact
Twitter : https://twitter.com/TulipToolsOU
Conclusion
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.
Language
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 :
- code modification : if the smart contract is about to change (adding a parameter to a function or adding a new field in the storage structure) then its implementation will be quite different and thus implies lots of modification of code because it will change the state of the stack and it is a bit hard to find where to add instructions to re-arrange.
- readability : for most of developers it is not effortless to read and understand low-level languages. Being able to trust a smart contract will improve attractiveness of the service. It will take a lot of times to understand the business logic of the contract if there are no comments neither annotations.
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.
Unit tests
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 :
- Write tests in the online editor and the output will be displayed on the right : it is nice if we want to test something quickly be it can’t be integrated in a CI.
- Write tests but use SmartPyBasic which will run the tests and put the result (with additional information) in an output file : the output will contains a result as OK or KO for each assertion. It is not a good thing to open then read this file to check if the tests are valid or not, this output should be in the Terminal and give more specific information about the error. If we want to integrate it in a CI, a script or a tool must be developed to parse the file and returns an error in case of a failing test.
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.
Integration tests
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, …).