Sol2ligo in action: migrating Solidity smart contract to Ligo (sol2ligo update #3)

Anastasia Kondaurova
Madfish Solutions
Published in
7 min readMar 12, 2020

--

We have already written about the translation challenges that we faced, so now it’s time to think how to overcome them. In this post, we will transpile real-world smart contract, compare the resulting code and apply some fixes to make it deployable.

For the most curious readers, we provide the JavaScript script to test compiled contract. It uses taquito to deploy and trigger smart contract functions in Babylon network.

Atomic swaps

For this purpose, we intend to transpile Atomic Swap. The main purpose of the dApp is to enable exchange between two cryptocurrencies without any centralized intermediary.

It is an awesome example as it contains structures, enumerations, various variable types, public non-payable, payable and view functions.

You may want to take a brief look at the original Solidity contract here.

Now let’s take a look at how sol2ligo translates different parts of that contract.

Structures

Structure in Solidity

It is a Swap structure that describes each exchange. timelock is time for tokens’ lock, value is the number of locked tokens, ethTrader is the token sender, withdrawTrader is the token receiver, secretLock is a hash for tokens’ lock and data hash of secretKey is equal to secretLock.

It is transpiled to:

Transpiled structure in Ligo

struct is compiled to record type. Each field is also compiled to the corresponding types: uint256 to nat, address to address, bytes32/bytes to bytes.

Thus, we have a structure declaration.

Default structure

Solidity sets variables to the default value automatically if the variable was declared but not defined. LIGO requires all variables to be defined during the declaration, so the transpiler puts default value in place of undefined variables for you automatically. To make resulting code cleaner, we declare special default structures at the top of the output file.

Structure filled with default values in LIGO

Enumerations

Enum declaration in

Variant type is the most direct analogue for enumerations in LIGO. But as of now, the comparison operation is not available for them in LIGO, so we do a little hack and declare each enum subtype as a unique unsigned number. Their names are becoming prefixed with enum name to avoid a collision. The first value of such enum is also considered a default value in future operations.

Transpiled enumeration in Ligo

Contract state

State declaration in Solidity

The state of this contract contains two mappings: swaps and swapStates. The first mapping is intended to store full swap data whereas the second one is used to track the swap stage.

All state variables in LIGO are wrapped into one record type. Fields inherit names and types from original contracts. However, there is no way to hide private or internal state variables, so we are simply omitting them. Thus, the state gets translated into the following record. Since LIGO is purely functional, later we pass one state instance to almost every function and expect to get its modified version as a return value.

Translated LIGO state

Event declaration

Events in Solidity

LIGO supports neither events nor logs. So we just comment them out with argument and its types to enable developers to use this information in the future and come up with some architecture solution.

Events translated to LIGO

Modifiers

Modifier declaration in Solidity

Contract modifiers of Atomic Swap are used to check conditions for each function triggering. The first four modifiers check if the swap stage is appropriate. The last one checks if the secret key matches. If conditions aren’t met, the transaction is reverted.

LIGO doesn’t have modifiers, so the compiler comments modifier declarations out and uses their code in functions directly. Here you can see how modifier gets inlined in LIGO:

Inlined modifier in LIGO function

Complex function

Complex function in Solidity

open is used to initiate swap, i.e. send tokens to the contract, lock it until locktime and send it to withdrawalTrader if the hash of provided secretKey is equal to secretLock.

Here Swap structure is created. It is stored in swaps map in contract state. Finally, the phase of the swap with the swapID is set to Open .

Open function in LIGO

In transpiled code, our function uses the same arguments as an original function but it has two return values for the list of operations and state types. It allows the function to make external actions and modify storage.

The first line of the function body is the inlined code from modifier onlyInvalidSwaps. In Solidity code it was a require statement preventing swap from being created if it has been created before, hence the assert statement appears in transpiled code.

New swap structure is created after the assert and stored into mapping. Later the swap phase is updated.

The event is skipped and the appropriate comment is placed.

Simpler function

Check function in Solidity

check is the function to be called to audit the swap, to ensure that all parameters are correct.

The view modifier and return values make this function interesting.

Due to it, transpiled version doesn’t return state. It returns only the list of operations.

Check function in LIGO

It also contains extra parameter receiver for the contract that requests its return values. At the end of the function, we have opList with a single transaction to the contract that will get back all the data about the swap. This is how external calls are implemented in LIGO.

Router

Solidity allows contracts to have many entrypoints while LIGO contract is expected to have one entrypoint called main.

Records generated for arguments in LIGO

To make all public functions available from main, we declare router_enum with subtypes named after functions and types of function’s arguments.

Router variant type (enum) in LIGO

Then this enum becomes the first argument of the main function and dispatches to the separate arguments that passed to the chosen function.

LIGO contract main function

Manual fixes

We transpiled the code, but it is still not ready for deployment. LIGO compiler will still complain about some things, so let’s catch more than a glimpse of them.

Invalid LIGO piece selected

LIGO yields error about the inability to put a dot following the closing parenthesis like ). Why is that? Because it doesn’t support such notation and dot operator can be applied to records only directly.

To mitigate this we simply need to split retrieving of the structure and accessing the field into two separate operations.

So now it becomes:

Fixed by declaring struct before field access

The same problem arises when accessing field is right after the map index access:

Again, the solution is to split it into separate lines, so the structure is declared beforehand, like so:

Rinse and repeat until you reach a valid contract and become ready for the deployment!

Testing

If you want to see transpiled contract in action, you may want to test it. You can deploy and test it manually or use our script instead. Follow simple instructions from repository Readme to launch the testing sequence.

Conclusion

As blockchain is developing and growing, the market needs more and more developers. It is hard to hop onto the train of the new smart-contract language quickly. Transpilers like sol2ligo are aimed at making this process simpler.

Developers can migrate solutions written on Solidity to Ligo. Most of the code will be generated by transpiler and and only few fixes will be required to make it compilable.

If you want to try it out right now, check out our new and shiny web version.

To look at the source code and CLI application, check out our repo.

Subscribe for updates

Facebook and Twitter

https://tezos.org.ua/ — Tezos Ukraine community website
https://twitter.com/UkraineTezos
— Tezos Ukraine twitter

--

--