Solidity to LIGO translation challenges (sol2ligo update #2)
We had a rough time building this system for the last month. LIGO is vastly different from Solidity, so we are slowly, but steadily converging them. As our goal is to get a few popular contracts transpiled in a meaningful way, we are prioritizing some features over another. One of the main pieces we have been working on is called Router.
A router is a system in Michelson (low-level language for LIGO) that allows to pass arguments and make a call to any method specified in the contract. In Solidity, It is quite easy to call a single method of contract from outside. Just get the name from ABI, properly arrange arguments and you are good to go. However, in LIGO you can only call the main
method. To work around the problem smart guys have came up with a router, namely: a special structure that allows users to pass the method name and its arguments straight into the main
method. It will take care of the branching, make a specified call and return its value. Usually, developers of the contract do it manually, deciding which methods are accessible from the main entrypoint.
However, we believe it is crucial for our transpiler to remove this burden from the developer’s shoulders and let the machine do its work (so to speak). Sol2ligo analyzes input code, finds all declared methods and determines if a function should be added to the router. We analyze Solidity modifiers like public
, internal
, pure
, view
to detect the most suitable way to include the method. We have also improved our state extraction code which picks methods that should accept and return a contract state depending on pure
/view
modifiers in Solidity input code.
Let’s take a look at how we translate a simple Solidity method declaration into a LIGO function:
As you can see, even the simplest methods have to be augmented with opList
and state
variables to achieve the same logic as they have in Solidity.
But if you want to call a method from outside it gets more complex since you have to provide a router, so, practically, every method is being translated into the following piece:
This one is much more complex. Initially, record
named <funcname>_args
containing all arguments is formed for every method. Then, a composite type variant enclosing this record is generated and put into router_enum
. Later, according to LIGO requirements, a method declaration is translated and modified to return both an operation list and a record
for keeping state. Finally, the main
function is created and it performs matching of input params with router_enum
for calling a specific action. To better understand why all these complications are necessary, you may refer to the Entrypoints section of LIGO documentation.
Conversion highlights
One may wonder how we translate such different languages and, to be fair, that’s not an easy task. We want to share some highlights and clever hacks we have had to apply to get the best-looking result. I’m not sure if they’d settle since every the smallest detail raises heated debates inside our team of how to provide the best result for an end-user. Thereby, I’ve collected a few interesting cases to look at.
Array declaration
It’s implemented this way because LIGO doesn’t have any array support and map
with an unsigned integer key is well suited for this job. Notice how we lose array size in the process - it doesn’t make sense to have a fixed length for a map
Map access
In Solidity, maps will return a default value if no value is found under the specified key. In LIGO we have to emulate this behavior by using one neat trick —case
is an expression! It enables us to use its results anywhere in the code, even on the right-hand side.
C-like for loop
This is the exact case of for
loop translation I have described in our previous story. Solidity doesn’t support for3
, so we have had to transform it into a while
loop. You can also notice that LIGO doesn’t have +=
operator, so this one has to be emulated as well.
Changelog
It was a fruitful month, so here are some key things we have been working on:
- Types and their default values: numbers, strings, bytes. Aliases for
bytes<num>
andint<num>
- Modifiers inlining, basic libraries unrolling, improved handling for
pure
functions - Comparisons, bitwise math, negations,
pow
- Cryptographic functions conversion
- Stubs for
event
andemit
, more warnings and more explanations - Improved type inference for number types and arrays
- Better tuples support
require
andassert
(shout out to LIGO team for helping out)- Basic support for
address
methods like (send
,transfer
) enum
andstruct
declarations and definitions- Tests for all of that
Work in progress
Lots of things are being changed, fixed and broken in the process. We change API and output trying to balance out readability and correctness. If you want to try it out be aware that’s an unventured territory, since no real-world testing has been done yet.
Check out repo at https://github.com/madfish-solutions/sol2ligo
Show us your support by leaving a star!
Subscribe for updates
https://tezos.org.ua/ — Tezos Ukraine community website
https://twitter.com/UkraineTezos — Tezos Ukraine twitter