What sets Ligo apart from Solidity?
Get a better understanding of what sets Ligo apart from Solidity and why Tezos is a better smart contract platform.
2,000 words, 10 minute read
One of the main differences between Ethereum and Tezos is the language developers use to write smart contracts. On the Ethereum side, you would use , the de-facto language for smart contracts on EVM blockchains. On the Tezos side, you may use , a functional language especially designed for Tezos that compiles to Michelson. Although the purpose of these two languages is to write smart contracts, they present huge differences due to the distinct features of the Ethereum and the Tezos blockchains.
In this article, we will explore these differences. After introducing the two languages, you will learn more about what sets Ligo apart from Solidity and how it makes Tezos a better and safer platform to build smart contracts.
Overview of Solidity and Ligo #
Solidity language #
Solidity is a high-level, statically-typed imperative programming language specifically designed for developing smart contracts on blockchain platforms, with Ethereum being its primary use case. As a key component of Ethereum’s ecosystem, Solidity enables developers to create decentralized applications (dapps) and self-executing contracts that autonomously operate on the blockchain.
Solidity compiles down to EVM bytecode, a low-level representation of smart contracts that consists of a series of hexadecimal instructions that are executed by the Ethereum Virtual Machine (EVM), the runtime environment for Ethereum smart contracts.
Ligo language #
Ligolang, often referred to as Ligo, is a domain-specific language designed for smart contract development on the Tezos blockchain platform. Ligo plays a crucial role in enabling developers to build secure and reliable smart contracts that run on the Tezos blockchain.
Ligo offers two different syntaxes: CameLigo that draws inspiration from OCaml and JsLigo that resembles TypeScript. Ligo is a functional programming language, and takes a statically-typed and formal approach to smart contract development. It provides developers with a set of tools and features that facilitate the creation of smart contracts with a strong emphasis on correctness and security.
Ligolang compiles down to Michelson, Tezos’ low-level stack-based language, the language that is stored and executed in on-chain smart contracts. This allows developers to write high-level smart contracts in Ligo, which are then compiled down to Michelson, the language executed on the Tezos blockchain. This abstraction helps developers write complex smart contracts while ensuring the generated code remains efficient and secure.
Main differences between Solidity and Ligo #
Although Solidity and Ligo serve the same purpose, i.e. writing smart contracts on a blockchain, they present a few noteworthy differences. Some of them are due to the general architecture of their respective blockchain, others are due to the design of the language itself.
We are going to review four of these differences that set Solidity and Ligo apart: their typing system, the difference between bigmaps and mappings, the entrypoint system and the order of transactions during contract execution.
This article will be based on the CameLigo syntax of Ligolang. However, the discrepancies from a high-level point of view between the two syntaxes remain minimal and most of the points made here about CameLigo should also be applicable to JsLigo.
Typing system #
let new_big_map = Big_map.update "test" 10 current_big_map in
current_big_map variable holds the bigmap that will be updated. You use the top-level function
Big_map.update and provide the key, the value and the bigmap you want to update as parameters. It will return a new big_map containing the previous values that were in
current_big_map and the new value you provided. This way of updating complex data structures is ideal to prevent unexpected side effects.
Another interesting difference in the typing system is how Ligo handles numeric values. No memory allocation for integers is required in Ligo as it is in Solidity: integer is just
int, there is no
uint256. Ligo doesn’t need an unsigned integer type as it has a
nat type that represents positive numbers. There are also no fixed point numbers in Ligo, which ensures the correctness of arithmetic operations. Ligo has 2 types to represent the native token of Tezos,
mutez. These types are guaranteed to represent XTZ values, they are always positive and they cause a runtime error when they overflow!
Because of its functional paradigm, Ligo also offers different types that are not available in Solidity and that contribute to the security of the contract execution: the
option type represents a value that may or may not exist, the
unit type is a placeholder similar to
tuple type allows the grouping of values of different types into a single value. Ligo also has enums called
variants that are more useful thanks to pattern matching.
Big map vs mapping #
Both Solidity and Ligo offer a data structure where data is stored as a collection of key-value pairs. In Solidity, such a structure is called a
mapping while in Ligo, it is called a
big map. There are some key differences between a mapping and a big map: mappings are virtually initialised such that every possible key exists and is mapped to a value whose byte-representation is all zeros, which is more gas expensive. On the other hand, big maps are initialised empty. The key of a mapping can only be a value type while the key of a big map can be of any comparable type, which allows more expressiveness and flexibility in Ligo. For example, you are not able to use a struct as a key in Solidity, while you are able to use a record in Ligo. There are restrictions in Solidity about passing mappings as function parameters while big maps are regular values in Ligo, they can even be passed as parameters to contract entrypoints!
Functions and entrypoints #
The first function you will probably write in your Solidity contract is the
constructor. It is the function that will be in charge of populating the different values in the storage of the contract when it is deployed, whether these values are passed as arguments or are hardcoded. In Ligo, you don’t need to write a constructor function. The storage will be set during deployment (called “origination” on Tezos) and all the functions marked with
@entry in the contract are entrypoints. You also don’t need to mark your entrypoint functions as
payable, etc. All the functions marked as
@entry are public entrypoints that can receive XTZ amounts and all the ones marked as
@view are views.
Due to the general architecture of smart contracts on Tezos, entrypoints always return a tuple with a list of operations and a storage. The operations in the list will be emitted and the storage will be saved. Entrypoints do not return other types of value that must be known in advance, which makes ABI JSON files irrelevant.
Differences in contract architecture #
A smart contract in Solidity and in Ligo will have a lot of similarities (functions as entrypoints, etc.), but also some important differences due to the blockchain they run on. These differences concern the way the contract is executed, the updates that are made to the storage and the way the storage can be accessed during the execution.
Although the syntax of the Ligo language makes it look like the entrypoints are independent of one another, the contract can be seen as a pattern match where the entrypoints are branches that are run according to the value provided to the contract call. This can be seen by the return type of the entrypoints, as they all return a tuple with a list of operations and a storage. In Solidity, every entrypoint is a distinct function that is called in its own context.
This particularity allows, for example, an important feature unique to the Tezos blockchain called formal verification. This is also a more functional approach where the contract is considered as a whole instead of being merely the sum of its entrypoints. This emphasizes the safety of the contract execution.
Storage updates #
Storage updates are also carried out in the safest way possible on Tezos. During the execution of an entrypoint, you will probably notice that any update you make to the storage of the contract will be automatically reverted if the execution of the contract fails. In Solidity, you would have to call the
revert function explicitly in order to revert any change that would have been made to the storage of the contract up to the point of failure and produce an error message. This functional approach guarantees that there are no side effects and that the storage is either completely updated or untouched.
Access to storage #
The main difference between Solidity and Ligo regarding access to the storage during the execution of the contract is also related to the functional nature of the Ligo language: in Solidity, when calling an entrypoint, the storage is represented by a global object in the contract that can be accessed from within the function. In Ligo, there is no global storage, the initial storage is passed to the entrypoint as a variable and will be returned at the end of the execution of the entrypoint. In this manner, the output storage is completely predictable in Ligo, because a given input storage will always produce the same output storage. Side effects are impossible.
In this article, we’ve explored the fundamental differences between Solidity, the language used on Ethereum, and Ligo, a language designed for smart contract development on the Tezos blockchain. While both languages serve the common purpose of writing smart contracts, their unique features and design philosophies are shaped by the distinct characteristics of Ethereum and Tezos.
Several significant differences were highlighted:
Typing System: Ligo leverages a functional paradigm that eliminates the need for value/reference type distinctions found in Solidity. Complex data structures in Ligo are updated immutably, enhancing predictability and preventing unexpected side effects. Ligo’s handling of numeric values and its unique types, such as
nat, contribute to security.
Big Map vs Mapping: Ligo introduces the concept of a “big map,” which is more versatile than Solidity’s mapping. Big maps in Ligo are explicitly initialized as empty, support keys of any comparable type, and can be passed as parameters to contract entrypoints, offering enhanced expressiveness and flexibility.
Functions and Entrypoints: Ligo’s approach to entrypoints is notably different from Solidity. In Ligo, functions are marked with
@entryand automatically become public entrypoints. There is no need to write constructors in Ligo, as storage initialization occurs during deployment. The return type of entrypoints in Ligo is a tuple containing a list of operations and the storage, emphasizing simplicity and ease of use.
Contract Architecture: Ligo’s execution model treats entrypoints as branches in a pattern match, ensuring the entire contract is considered as a whole. This approach supports formal verification, enhances safety, and is characterized by a functional, side-effect-free execution. Storage updates in Ligo are automatically reverted if the contract execution fails, promoting data consistency. In Ligo, access to storage is predictable and entirely determined by input and output, preventing side effects.
In conclusion, while Solidity and Ligo share the objective of smart contract development, they diverge significantly in their design philosophies and approaches. Ligo’s emphasis on formal verification, immutability, security, and predictability makes it a compelling choice for developers looking to build smart contracts, offering distinct advantages compared to the more familiar Solidity used on Ethereum.