Sol2ligo in action: migrating Solidity smart contract to Ligo (sol2ligo update #3)
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
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:
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.
Enumerations
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.
Contract state
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.
Event declaration
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.
Modifiers
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:
Complex function
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
.
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
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.
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
.
To make all public functions available from main
, we declare router_enum
with subtypes named after functions and types of function’s arguments.
Then this enum
becomes the first argument of the main
function and dispatches to the separate arguments that passed to the chosen 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.
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:
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
https://tezos.org.ua/ — Tezos Ukraine community website
https://twitter.com/UkraineTezos — Tezos Ukraine twitter