Solidity vs. Ligo: The Key Differences

Written by claudebarde | Published 2020/05/04
Tech Story Tags: tezos | blockchain | smart-contracts | ligolang | solidity | latest-tech-stories | what-is-the-ligos-language | what-is-the-solidity-language | web-monetization

TLDR The Tezos blockchain is one of the main projects you need on your radar for 2020. Some concepts that you know from Solidity do not apply (and can even be counterproductive) when coding with Ligo do not exist in Solidity. Ligo has tuples, sets, and lists but no arrays, allowing for a better structure and organization of your code. In this article, we’ll look at the main differences, pitfalls, and things to keep in mind when you are used to Solidity and want to join us.via the TL;DR App

What you need to know about writing smart contracts on the Tezos blockchain when you come from Ethereum

The Tezos blockchain is one of the main projects you need on your radar for 2020. Its liquid proof of stake system makes it increasingly attractive for people looking for steady revenues. Its token has more than doubled in price since last December, making it interesting for investors. And there are many new projects planned to be deployed on the blockchain in 2020 (an NFT-based gameUSDTez, and the new tzBTC to name a few).
You have some experience with the Ethereum blockchain and you’re thinking about taking the plunge and getting your feet wet with Ligo. Congratulations! However, you’ll quickly notice that even if they’re both labeled “blockchain,” programming on the Tezos blockchain and the Ethereum blockchain can be as different as programming in two different languages. Some concepts that you know from Solidity do not apply (and can even be counterproductive) when coding with Ligo. Likewise, some concepts in Ligo do not exist in Solidity.
In this article, we’ll look at the main differences, pitfalls, and things to keep in mind when you are used to Solidity and want to join us and become a Tezos smart contract developer.
I’ll assume that you have a (moderately) good knowledge of Solidity and JavaScript. No knowledge of Ligo is required, although it may help you to better understand the comparisons.

The Type System

The type system for simple values in Solidity and Ligo are very similar. Ligo has integers, strings, addresses, nat (the unsigned integer of Solidity), booleans, etc. One of the perks of types of Ligo is that you can declare your own types, it allows for a better structure and organization of your code. For example, it may be clearer to sometimes create a new type instead of using a primitive type (
username
 instead of 
string
score
 instead of 
nat
).
The complex types present more differences: Ligo has tuples, sets, and lists but no arrays. Sets can be seen as unordered arrays, containing unique values of the same type, while lists can be seen as ordered arrays containing values of the same type. Ligo also provides a number of methods to read, modify, or return information about the sets and lists, which are very handy.
Ligo also offers 
records
 which are very useful and similar to 
structs
 in Solidity. Ligo doesn’t have enums, but variants that give you all the functionalities of enums and additional benefits! A special kind of variant called 
option
 will spare you a lot of bugs when verifying the right value is provided.
Another major difference between Solidity and Ligo is in its maps (the mappings of Solidity), which is the subject of the next paragraph.

Maps and Big Maps

Ligo offers maps and big maps, which behave essentially in the same way. Big maps are more suitable for containing a large amount of data. They resemble mappings of Solidity with a few benefits.
In Solidity, all the keys of a mapping exist by default, if you try to get the value associated with a key that doesn’t exist, you will get the default value of its type. This is not the case in Ligo. Thanks to pattern matching, you know precisely when you query a value in a map whether it returns something or not. If nothing is returned, you get an option with the value None. You aren’t left wondering whether the 
0
 or the empty string returned is the actual value or the default value because the key doesn’t exist!
In Ligo, it’s possible to iterate over a map (this is not possible natively in Solidity with mappings). When iterating over a map, you can just check the returned values, you can modify them or you can “fold” them. One feature that takes some time to get used to when coming from Solidity (or a language like JavaScript) is that the functions you will use are not methods of the instance of the map (or the set/list) but methods of the Map (list/set) object. For example, if you want to know the size of a map you wouldn’t write 
my_map.size()
 but 
Map.size(my_map)
. To add an element to a set, you wouldn’t write
my_set.add(el)
 but 
Set.add(el, my_set)
.

String Manipulation and Sets/lists

I put these two different cases together as they are little highlights that will make your job easier and your code safer.
Solidity is infamous for lacking any sort of native string manipulation — you can’t slice them, concatenate them, etc. Rejoice, because Ligo lets you do it! You get three methods that will help you work with strings. 
String.length()
 will return the length of the string, 
String.sub()
 will return a substring of a string and strings can easily be concatenated in ReasonLigo with the 
++
 operation (
string1 ++ string2
).
I particularly enjoy working with sets and lists when writing smart contracts in Ligo. Sets are unordered lists of unique values of the same type. A few functions help you manipulate them easily (
Set.add()
Set.remove()
) and you can be sure you have unique non-repeated values in them.
Lists are ordered collections of elements of the same type. If you need to store some values in a certain order, this is what you use. Different methods help you work with lists, like 
List.iter()
List.map()
 and 
List.fold()
. These types add another level of security and robustness to your smart contract.

Contract Deployment and Initialization

One of the highlights of being a smart contract developer is deploying your contract to the blockchain and letting it start its self-contained life!
When you write a smart contract in Solidity, you usually start by building the constructor. This function is in charge of initializing storage during the deployment of the contract. This doesn’t exist in Ligo. There’s no constructor function. Instead, you provide the initial storage while deploying the smart contract! In the Tezos lingo, it is called “originating a contract”.

Entry Points

The logic of using a smart contract on the Tezos blockchain is somewhat different from that of a contract on the Ethereum blockchain. In Solidity, you have multiple access points of your contract that you can call directly to modify or return some data. This creates the need for a “fallback function” in case users try to call a function that doesn’t exist.
In Ligo, there’s only one single access point for your smart contract called the entry point. This may sound a little weird at the beginning (it did to me) but it’s actually a robust system that wipes out the need for a fallback function. Your main entry point receives the name of the function you’re trying to call as a parameter and will use pattern matching to ensure that the function exists. Then, it will call the matching function called a “pseudo-entry point.” If it doesn’t exist, it will just fail. In Solidity, calling a function that doesn’t exist triggers the fallback function. In Ligo, it throws an error.
Note: The main entry point of your smart contract in Ligo only accepts two parameters: one parameter sent with the transaction and the storage. It’s not possible to provide multiple parameters like in Solidity 
function myFunc(param1 uint, param2 uint, param3 string)
. However, you can send a record to the function. This restriction is helpful in avoiding common bugs in Solidity when passing parameters in the wrong order.

State Updates

One of the other main differences between Solidity and Ligo is in the transaction flow. In Solidity, when you send a transaction to a smart contract, the state of the smart contract is updated along the way until the transaction reaches the end of the function. If there’s an error at any point in the flow (for example thrown by an 
assert
 or 
require
 exception), the state is reverted to its original value before the transaction happened. This leaves your code open to reentrancy attacks if you are not careful about when and how you update your state.
Reentrancy attacks are unlikely with a smart contract on the Tezos blockchain. In Ligo, the main entry point always returns two things: a list of transactions to execute and the new state. At no point during the transaction flow is the storage of your smart contract altered. If there’s an error, the transaction will just stop with nothing changed. Since the function returns the whole storage, pattern matching will make sure that you return correct storage with the expected values. If a property of your storage is supposed to be a nat, it will be impossible to store an integer instead.

Pattern Matching

Pattern matching is among the features of a smart contract in Ligo that will help you make it bug-free. Below are two examples of pattern matching, but this is a built-in feature that will prove itself useful all over your smart contract!
In the pseudo-entry points
As mentioned earlier, there is only one “true” entry point in your Ligo smart contract. Through pattern matching, it will be in charge of redirecting the transaction call to the right function. Let’s check the example provided on the Ligo website:
When the transaction reaches the smart contract, it’s evaluated against its name and its parameters. If the type of the parameter matches, it will go through the switch pattern to be redirected to its corresponding function. After the function processes the transaction, the new storage is returned.
In updating maps
Pattern matching is a powerful ally when you want to update a map.
In Solidity, you have to take a wild guess when updating a mapping and assume the key has been initialized with a value (because all keys exist by default). Sometimes it’s a desirable pattern — for example, if you want to create a new key because adding a value to a key that was not previously initialized in a mapping will just create it.
In Ligo, it’s impossible to add a value to a key that doesn’t exist in a map. Every search and update of a map goes through pattern matching. Imagine we have a mapping of the type 
map(address, tez)
 that stores the number of tezzies owned by our users. This is how you would update the value for a provided address:
switch (Map.find_opt (user, wallets)) {
  | Some (wallet) => Map.update(user, wallet + 1tez, wallets)
  | None => failwith ("No wallet found") : return_type
}
Here’s what goes on in this little piece of code.
Map.find_opt
 is a function that returns an option type, i.e a variant type that contains only two options. Either there’s something (
Some
) or there is nothing (
None
). If a value associated with the key was found, the value is returned with 
Some
. Otherwise, 
None
 is returned (in this case, we just throw an error, but you could also create the key/value pair or complete another action). It’s then impossible to try to update the value of a key that doesn’t exist.
In Solidity, you would write 
wallets[user] += 1 ether
 because by default the key exists and you have no control over its existence. The wallet could also be equal to 
0ether
 and you wouldn’t know if it is a desirable value or just the default value. In Ligo, if the value of a key is 
0tez
, you know that it was set to be equal to 0 on purpose.

Storage Reading

One of the most puzzling differences between Ligo and Solidity for me, when I started writing smart contracts for the Tezos blockchain, was how to get the values from the contract storage out to my dapp. As mentioned earlier, the entry points of a Ligo contract always return the whole storage and a list of operations. There is no getter function to implement if you want to return one single value. Also, the storage in your Ligo smart contract is one single variable (generally a record), instead of distinct separate variables as in Solidity.
I only got a satisfying answer to this question when I started playing with libraries for dapps, like the excellent Taquito. For every smart contract developer coming from Solidity and wondering how Tezos smart contracts export their values to use them in a dapp, the simple answer is: they don’t. They won’t return any value but you can read them from outside. The Tezos node exposes the contract storage and libraries like Taquito give you access to the whole storage (for example with 
await contractInstance.storage()
 if using Taquito).
Solidity has to automatically create getter functions for your variables, while this is not a concern with Ligo. In Ligo every value of the storage is always one promise away from you!

Desirable Features From Solidity

Now that I’ve spent the whole article trying to show you how amazing writing smart contracts for the Tezos blockchain is, let’s see a few things that you’re going to miss as a Solidity developer when switching to Ligo.
Obviously, the Tezos ecosystem hasn’t yet reached the maturity of the Ethereum one and a lot of tools and features are yet to be developed. Here are a few that will hopefully make their way into the smart contracts and the Tezos ecosystem:
  1. Events. Unlike in Ethereum, there are no events in Tezos smart contracts. You won’t be notified that the state of the smart contract changed. Taquito has a function to listen to storage changes (
    Tezos.stream.subscribeOperation
    ) but I tried it on a sandboxed node and half of the storage updates didn’t register. As of today, the most reliable way to get notified of storage updates is a good old 
    setInterval
     with a function that compares the values of the freshly fetched storage and the old one.
  2. Remix. Remix is like the Holy Grail of writing, testing, and deploying smart contracts online. Ligo (and SmartPy for that matter) have done a great job creating online editors but they still feel flimsy compared to Remix. With the Ligo editor, you can write your smart contracts (the syntax highlighting and autocompletion of variable names and methods/properties are neat), compile them to Michelson, test your entry points and deploy it to Carthagenet (in theory). However, the editor doesn’t let you save your smart contract — a wrong swipe on my Magic Mouse meant that several times I had to start again. It takes a few try-and-fails to understand how to format the initial values of your storage to test the smart contract entry points. Finally, there is no JavaScript VM for testing.
  3. MetaMask. Sorry to be the bearer of bad news, but there’s no MetaMask for the Tezos blockchain. You’re left wondering how the users of your dapp can sign their transactions until you find (and accept) TezBridge. TezBridge is a great tool as it allows dapp users to sign their transactions without installing anything and it integrates nicely with Taquito. However, you are going to miss the elegance, simplicity, and convenience of MetaMask. You won’t be able to check the history of your transactions, you won’t see your balance, the tokens you own, and you won’t be able to send tezzies from the interface. TezBridge does just one thing, help you sign your transactions, and it does it well!
  4. The tools in general. Because Tezos is only two years old, the available tools to help you develop smart contracts and dapps are still in their infancy and pretty scarce. The ones that are available are generally not beginner-friendly, as you must have a good knowledge of Michelson and/or functional programming to understand documentations that are either incomplete or highly technical. If you’re a beginner developer in JavaScript, it can take a few hours to write a simple smart contract in Solidity, figure out how to test it in Remix, spin up Ganache and Truffle and build a simple dapp with web3js. It won’t happen when you switch to Tezos (at least for the moment). You’ll have to do some research, read documentation, bug the amazing and very helpful people on the Ligo Telegram, and try and fail until you find the right way to do it.
  5. The resources. There are countless tutorials out there to guide you, whether you want to create a simple smart contract in Solidity, use OpenZeppelin, or create upgradable contracts or develop the next DeFi hit. This is not the case with Tezos. You’ll find some tutorials for simple things but for more advanced concepts tutorials are scarce or nonexistent. Learning Ligo will require you to have a basic understanding of functional programming (although ReasonLigo makes it a lot easier). Many of the smart contracts out there I could find for references are written in PascaLigo, which adds another level of complexity for beginners.

Conclusion

As you can imagine, switching from Solidity to Ligo requires patience and a lot of reading. You have to learn how to use a new programming language for smart contracts that’s pretty different from Solidity. You need to find new tools to help you test your smart contracts and develop your dapps. You need to learn how to interact with smart contracts on Tezos from your application.
If you are a self-taught developer like me, it will take you some time to figure out how everything works together. However, it’s a very rewarding process. During these past months, I’ve learned a lot about functional programming, smart contracts, Tezos, and dapps in general. If you continue writing smart contracts in Solidity, a glimpse of another approach to creating them may help you become a better developer. Just be cautious — writing smart contracts for Tezos is highly addictive, as soon as you realize how much secure, robust, and elegant they are!
Don’t hesitate to leave your opinions or suggestions!

Written by claudebarde | Tezos smart contract developer in the making 🌮
Published by HackerNoon on 2020/05/04