The Structure of an LLL Contract — Part 2

Initializing our understanding of structure

Daniel Ellison
ConsenSys Media
8 min readJun 14, 2017

--

In the previous article in this series, we started examining the structure of an LLL contract. Amongst other things, we found out that there are two main sections in any LLL contract that adhere to the Ethereum Contract ABI, and that these sections are often marked by the comments INIT and CODE. In this article we’re going to delve into the INIT section and find out how constructors are represented and handled in LLL. But first we’ll talk a little about including other files in your contract. There’s a companion screencast for those who prefer that.

Being inclusive

It’s pretty straightforward: To include the contents of another source file within your contract, you use the include keyword, specifying the file you wish to include:

include simply reads the source code contained in the specified filename and places it into your source as if you had typed it at that point. It’s very similar to the include functionality found in many other languages. You can use a fully specified path, a partial path, a relative path, or just the filename itself if it’s contained in the same directory as your source file.

In this particular case I’m including macros.lll in order to have all of the macros I’ve written for this contract available from the start. I could have just written them in the contract’s source, but I like to keep things separated for clarity. I’ll be going deep on LLL macros in a future article. For now I’ll just say that macros are a great way to tidy up your source code and make it easier to read. To avoid confusion I’ll call out any time I’m using a macro in this series.

The INIT section

Ok, let’s get down to business. Here’s one of the places that LLL differs structurally from Solidity. For those who aren’t looking at structure.lll, I’m including its INIT section here (without comments):

Note: use of the panic keyword will soon be discouraged. Instead, a new keyword, revert, is being added to the compiler which will eventually provide a kinder exit. panic will be used for things like program logic errors.

As I mentioned in the previous article, you can think of the INIT section as being similar to Solidity’s constructor function. In actual fact, under the covers Solidity’s constructor works in exactly this way. The bytecode generated by solc has this same initialization section which retrieves the constructor parameters in the same manner as we’ll be going into here.

As a quick note in case it’s not obvious, comments in LLL (as in Lisp) are denoted by a semicolon. There is a comment convention for Lisp that — after a fashion — I follow in LLL. You don’t have to do the same, but the conventions have been in use for many decades so if you do use them you’ll be giving visual clues to those who are familiar with them.

The constructor

The lines of code that you see above comprise the LLL constructor in this particular case. Constructors can do more or less than that; often there’s no code in the INIT section at all in the case of a contract that needs no initial parameters. Whether you provide constructor code or not, if you follow ABI conventions and provide a code body your resulting binary will still have an initialization section; the compiler inserts instructions that return the CODE section to the caller. This is what the assembly output looks like when our constructor above is compiled:

As you can see, these two lines of assembly:

are extremely close to their LLL equivalent — aside from the macro constants which are enclosed in asterisks:

This illustrates how low-level LLL really is. With all of that in mind, let’s discuss how the constructor’s parameters are passed in and then retrieved.

Note: The reason the macro constants are enclosed in asterisks is to differentiate them from macros that have functionality. This too is being addressed with a compiler change that will soon be available. Until then I’ll leave the asterisks in place for clarity.

Deployment vs. invocation

One thing you need to keep in mind while we discuss the INIT section is that there are two distinct phases to this process: deployment and invocation. Both cause their own separate blockchain transactions, but the deployment is only executed once whereas you can invoke your contract’s functions as often as you wish. All of the following — with minor differences — applies equally to Solidity and LLL.

When you compile your contract it produces the bytecode that will be executed when your contract is deployed. This includes the initialization section as seen above, followed by your contract’s functions. Any constructor parameters passed into the contract when deployment takes place are dynamically appended to the compiled bytecode:

Deployment involves two things. First, your constructor code is executed. Second, the length and start location of your contract’s functions is retrieved and then copied to 0x00 in memory for use by the final return. At that point, deployment from our perspective is complete. Let’s look deeper into the execution of the constructor.

Constructor execution

We needed to learn about contract deployment in order to understand how a contract retrieves its parameters. As stated above, any constructor parameters supplied during deployment are appended dynamically to the deploy-time bytecode. Consequently, your constructor needs to retrieve these parameters in order to do its work. This is done with the help of the bytecodesize operator.

Keeping in mind that we’re in the deployment stage, the bytecodesize operator returns the length of the currently-running code minus the dynamically-added constructor parameters. That gives us a convenient offset from which we can access the parameters.

If you read part 2 of my article Deploying your first LLL contract, you know that we need an ABI definition for any contract that we want to be accessible by web3. In the ABI definition for this contract is a section that describes the constructor’s input parameters:

From the JSON above we know that we can expect a uint256. The uint256 type isn’t encoded in any particular way; it’s just sent through as a 32-byte number. As a result, for our example contract we don’t have to decode anything.

Deconstructing the constructor

Let’s go back to the code for our constructor. Before we describe parameter retrieval there’s one more thing to explain. There are two lines at the start of the INIT section that check callvalue and abort if it’s non-zero:

This is the inverse of Solidity’s payable check. In fact, later you’ll see a macro that encapsulates these lines under the symbol unpayable. Interestingly, the Solidity compiler is doing exactly what we’re doing here. You specify payable on functions that you want to accept payments. Behind the scenes solc is in effect inserting unpayable in front of every function that you don’t want to accept payment.

This is another of Solidity’s many conveniences. Of course, as with most conveniences there’s a cost involved. Without you knowing it (or maybe even assuming the opposite), solc is not affecting the functions you want to be payable, it’s adding code to every other function that isn’t payable! At least it’s explicit in LLL. I hew to the aphorisms in The Zen of Python, in this case explicit is better than implicit.

For argument’s sake

Ok, now we’re down to the part of the constructor that I’ve been dancing around for the whole article: argument retrieval. Recall that these two lines comprise the true functionality of the constructor:

With everything we’ve discussed previously, this code should now be much easier to understand. The first line does the actual argument retrieval. The codecopy operator copies bytecode from the currently-running contract to a specified location in memory. So here, it’s copying to *scratch*. This is an easy way to slide you into an understanding of LLL macros. In macros.lll there’s a statement that looks like this:

This is what I call a constant macro. It simply defines the symbol *scratch* as 0x00. Wherever you use *scratch* in your source, the compiler replaces it with 0x00. I’ve already described bytecodesize as returning the size of the deployment bytecode. This size can also be seen as an offset from the start of the deployment bytecode to where the constructor parameters reside. Let’s put it all together:

In this case, codecopy takes the location and length of the constructor’s parameter and copies it into memory at *scratch*, or 0x00.

Being persistent pays off

It took a while to get here but in the end it’s pretty straightforward. There’s only one step left before we’re done with the INIT section. codecopy can only copy to memory, not storage. If we want to make the argument persistent so that our contract’s functions have access to it post-deployment, we’ll have to copy it from memory to storage. sstore takes a storage location and a value and stores that value in the given storage location. In this case, sstore takes the value stored at memory location 0x00(mload *scratch*) — and copies it to *parameter*, which has been predefined as 0x00.

Conclusion

That was much longer and more technical than any of my previous ConsenSys Media articles. We’re at a point where that shouldn’t matter if you’ve been following along. If you have only encountered this article, I would suggest going back and starting with An introduction to LLL for Ethereum Smart Contract Development, continuing on from there. By the time you get back here you’ll be in a position to better understand the information I’ve presented.

As I’ve said before, this all may seem complex and a little intimidating, but with use it will become second nature. Please ask any questions you might have in the comment section below. In the next article we delve into the inner workings of the CODE section, which can also be described as your contract proper. I hope this has been helpful and that the information has been presented in a clear, concise manner.

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 3

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.

--

--