The Structure of an LLL Contract — Part 3

In which we conclude our exploration of structure

Daniel Ellison
ConsenSys Media
6 min readJul 3, 2017

--

In the previous article we examined the INIT section of an LLL contract. This time we’re going to cover the CODE section of that same contract. The CODE section is equivalent to the set of functions in a Solidity contract, excluding the constructor. Keep in mind however that these sections are merely convention. An EVM contract is simply a sequence of instructions. The idea of “sections” is an artificial construct; a contract is just a program that’s executed in two different contexts.

If you prefer, there’s also a screencast that covers this material. If you want to follow along at home, take a look at the source code for our example contract.

Recap

On deployment, a contract — which includes both the INIT and CODE sections at that point — is executed in a single invocation. The purpose of this is to set things up for your contract functions and then bundle up and return the bytecode for those functions. That’s all the INIT section does. Since this happens after compilation, the full bytecode for your contract includes compiled versions of your functions. This function bytecode is returned to the caller for deployment to the blockchain.

So you can see that the first time through, the contract includes the initialization code and your functions. After deployment only your functions remain. Those functions are not executed on deployment: they’re merely data to be returned by the initialization code.

The CODE section

Now we get down to today’s topic: the CODE section. As stated previously, this section contains the functions you wish to make available from your contract. In our source, the code section officially starts with returnlll. This is the agent by which your function bytecode is returned to the caller. Recall in the second part of this series we showed what the assembly output of the INIT section looked like. returnlll is responsible for generating the following code:

dataSize(sub_0)
dup1
dataOffset(sub_0)
0x0
codecopy
0x0
return

As discussed in part 2, this code determines the length and start location of your contract’s functions and then copies the bytecode to 0x00 in memory for use by the final return. One thing to note: returnlll is actually a convenience macro built into the compiler. It simplifies the process of returning the function bytecode. As such, it’s not a control statement so it doesn’t serve as an enclosing keyword like while, for example. We need to use seq for that, as shown on line 26 in the source.

Functions

To steal from The Matrix, there are no functions. Whenever you invoke a function on a Solidity contract, it’s actually performing something more akin to a switch statement behind the scenes, where each function is a case. This is important to understand for efficiency’s sake. switch progresses through its cases until it finds a match. If your contract has many functions, the ones further down the list will cost more to invoke. This is because the more distant a function is, the more comparisons have to be made to match it. It’s not a huge cost, as the Solidity bytecode calculates the requested function ID once and pushes the result onto the stack for future comparisons, but every bit of gas saved helps.

Now we’re ready to tackle the next statement in our code body:

get-function-id

This is a macro which does the equivalent in LLL to Solidity’s storage of the function ID. You don’t need to know how the macro works for now, but it essentially calculates the requested function ID and stores it in memory for future use. I’ll be doing an in-depth coverage of macros in an upcoming article. I’d like to thank Ben Edgington for pointing this issue out to me.

We’ve arrived at the one function in this contract:

(function *get-constructor-parameter* ...

Remember: there are no functions. The function instruction you see is another macro I’ve written to clarify LLL source code and make it feel somewhat more familiar to a developer.

Next, you see a seq statement with what looks like a parameter:

(seq unpayable ...

This is a convention I came up with to indicate what I see as the equivalent of using a Solidity modifier, such as payable. Recall from the Deconstructing the constructor section of part 2 that we need to invert the payable check in LLL as that’s actually what’s happening behind the scenes in Solidity. unpayable is macro that checks whether ETH was sent into this function and aborts if true. If you had more LLL modifiers you would put them before or after unpayable. Order matters, so think about what should be tested first, second, etc.

If the function survives the unpayable macro, it simply retrieves the constructor parameter from storage and puts it in memory at *scratch* then returns it. I’ve kept this function simple to avoid unnecessarily lengthening this article.

Fallback

There’s one other thing to cover before we’re done with the CODE section: the concept of a fallback function. If you don’t specify one, Solidity inserts a fallback function for you which simply throws if no functions match the one you specified. It’s sort of like a switch statement’s default case, and a fitting way to end our discussion on the structure of an LLL contract.

Since we don’t have the help of the Solidity compiler, we need to explicitly add a fallback function. It’s not even a pseudo-function as we explored above. In our case if the function matching fails, program flow falls through to the panic keyword you see at the end of the source. This causes an EVM exception and halts the contract’s execution. Pretty simple now that we understand the mechanics of contract execution.

You can do anything you like in the fallback function; it’s not restricted to a simple panic. As long as you enclose multiple statements in a seq or other control keyword you can go wild.

Wrapping up

The major ideas to take away from this series are:

  1. An EVM contract is aways a single statement. It can be as simple as an integer or as complex as the ENS Registrar contract.
  2. Since the compiler expects a contract to be a single statement, you must enclose multiple statements in a sequence.
  3. There are two distinct contexts in which your contract’s code is executed: deployment and invocation.
  4. There are no constructors. During deployment, code can be executed to initialize your contract’s storage if necessary, but that code is not deployed to the blockchain, so the “constructor” can never again be invoked.
  5. There are no functions. Invoking one actually causes the equivalent of a switch statement to occur.
  6. Solidity does things behind the scenes that make contract development more familiar to those new to the space, but these conveniences are usually at a cost, both monetarily and intellectually.

Conclusion

I hope this has been an interesting journey! I think it’s extremely important to understand what’s really going on when you deploy and execute your contracts. The information presented here should be useful to you even if you never write an LLL contract, though I hope you consider doing so. Any feedback on this or the previous articles in this series would be greatly appreciated.

Read More:

An Introduction to LLL for Ethereum Smart Contract Development
Building and Installing Ethereum Compilers

Compiling an LLL Contract for the First Time
Deploying Your First LLL Contract — Part 1
Deploying Your First LLL Contract — Part 2
The Structure of an LLL Contract — Part 1
The Structure of an LLL Contract — Part 2

Like this piece? Sign up here for the ConsenSys weekly newsletter.

Disclaimer: The views expressed by the author above do not necessarily represent the views of Consensys AG. ConsenSys is a decentralized community with ConsenSys Media being a platform for members to freely express their diverse ideas and perspectives. To learn more about ConsenSys and Ethereum, please visit our website.

--

--