A technical primer on using encoded function calls

A developer’s guide to encoding and executing encoded functions.

Encoded function calls are powerful tools, used by having a bytes parameter, like those found in the ERC721 and ERC223 standards. These bytes parameters allow you to nest function calls within other functions. This has many benefits such as expanding the utility of a token contract or creating generic public functions with validation.

Bytes parameters are usually included as a forward compatible measure, which allows the function to send through any additional data that is required, provided that it has been encoded into bytes first. This provides you with nifty upgradability patterns and you can even embed one into a QR code.

Image by Kevin Ku from Unsplash

Let this article serve as a guide, covering everything you need to know about encoding in both Solidity and JavaScript, as well as how to execute an encoded function call in Solidity.

What does it mean to encode a function anyway?

From a developer’s point of view, an encoded function is a function that has been transformed into (EVM’s) bytecode. Before you start encoding a function call, I almost insist that you go and read the docs. They will give you some important warnings and valuable advice. Disclaimer! All the code snippets are in pragma Solidity 0.4.24. There are breaking changes in 0.5.0!!

What parts of a function can you encode?

A function can be broken up into components, mainly the function name, the function parameters, and the function signature.

The components of a function

The function signature is the function name with the parameter types, as shown below:

createLime(string,uint8,uint8,uint8);

A function call is the function name with the parameter values:

createLime(“string”, 1234, 4321, 1234);

Below is a simple example of encoding the function's signature.

The encoded function signature is: 0xe0b6fcfc and is a bytes4.

Important to note

You can only execute public or external encoded functions. (Please see ‘executing an encoded function call’ further on in this article).

The examples shown here are written in pragma 0.4.24. If you want it to be brought up to pragma 0.5.0 please leave a comment. This point has previously been mentioned, but it is incredibly important to highlight once again.

Encoding your function

To encode your function in Solidity, we will use the keccak256 hashing function with a conversion to bytes, which is explained later.

What you’ll need to encode your function

Before getting started, you’ll need a decent understanding of JavaScript and Solidity. You will need Truffle , or your testing framework of choice, and the web3-eth-abi library. It should be noted that this is only available with web3 1.0.0 beta).

Encoding and executing an encoded function call in Solidity

To encode a function signature in Solidity:

Notice how there are no spaces between the parameters? You’re hashing the function name with keccak256(), a hashing function and returns hex, which is then converted into bytes4 . This converts it from hex to an array of 4, 8 byte numbers. This is actually how Solidity creates function signatures for the EVM.

In Solidity 0.5.0, you can target the interface, followed by the function, which calls the selector property as shown below:

//So this bytes4(keccak256(“onERC721Received(address,address,uint256,bytes)”))
// is equal to this
IERC721Receiver(0).onERC721Received.selector

To encode a function call in Solidity:

You don’t need to encode the entire function call in Solidity. If you have the parameters, you can execute it. See below.

To execute an encoded function call in Solidity:

Be warned: your this.call(); should always be encapsulated in a require(); ALWAYS! The this.call(); will return a boolean for its success. If you don’t put it in a require(); and it fails, you will not know.

A side note, in 0.5.0 this.call() returns a boolean and some data about the call, which will break your require()’s. By using the this.call() you pretty much hand over control of your contract, but you’d already know this if you’ve read the docs. Executing can be done in a couple of ways, the first being with un-encoded parameters and an encoded function signature as shown below:

The this.call(); is a low-level call and Solidity will warn you that low-level calls should be avoided. The reason for this is that if you don’t validate the encoded call you are receiving, this call could wreak havoc. You are mildly protected by the fact that this in the this.call(); makes the call seem like it’s coming from an external source, meaning that only public and external functions can be called. Expect this call to be irrational, and not what you initially planned it to be used for, and you should be fine.

Another possibility for running an encoded function call is if the entire function call is encoded. This is the same as when you would use the this.call(); again.

Be warned: executing an encoded function like this could have some serious negative consequences. For example, if you want to verify the function signature you now need to extract it. Trust me when I say that it is not trivial and will involve assembly.

Finally, you can make an encoded function call in Solidity by encoding it directly in the this.call and then adding in the parameters.

If you think about it in terms of security, you know with 100% certainty what function this will call irrespective of the parameters received. See how the function signature is encoded in the same way as before, but this time we have instead encoded it directly at the time of calling it in the this.call();. You could also call the function directly, but I included this so you would know it’s in the realm of possibility.

You can even pass in the encoded function signature and encoded parameters separately. This makes it easier to verify the function signature as shown below:

Encoding in JavaScript

There are many ways to ‘skin this cat’ when encoding a function in JS. You can encode a function with its ABI array by using the web3-eth-abi. Note that all the examples that are given here are written as Truffle tests with the actual testing parts excluded. If you would like a link to the repo so that you can fiddle around with it yourself, just drop a comment at the end of this article.

To implement the web3-eth-abi library simply add const web3Abi = require(‘web3-eth-abi’); to the top of your JS.

To encode a function signature in JavaScript:

There are multiple ways to encode a function signature. You can use the functions ABI array as shown below. Notice how we are using the web3Abi‘s encodeFunctionSignature function to do the encoding for us.

To find the ABI array of the function, go into your build directory to the specific contract and ctrl + f to search for your function name. A heads up, if you change your function’s parameters you are going to have to get the new ABI, so this isn’t recommended for a function that might change. Instead, use the method provided below.

You can also encode the string of the function like in Solidity. Notice how we use the same encodeFunctionSignature function.

Much like the Solidity encoding of a function signature, there are no spaces between parameters.

To encode function parameters in JavaScript:

Each parameter has to be encoded by itself and you would have to feed them into the function call separately. There may be the ability to encode multiple parameters at once in a newer version of the web3-eth-abi, or in a newer version of Truffle.

To encode a function call in JavaScript:

Here, we use the web3Abi‘s encodeFunctionCall to encode the entire function call. To add the parameters, [‘a string’, ‘1234’], simply list them after the closing curly bracket of the function’s ABI array inside single quotes in the order that the function states. Make sure your parameters are still within theweb3Abi.encodeFunctionCall() function call bracket.

To execute an encoded function call in JavaScript:

Obviously, this isn’t possible when you are trying to execute the call in the contracts, but I thought I would still leave this here to assure you that: it’s not possible.

In conclusion

No matter what you are using your encoded function call for, ensure that you do proper validation to prevent attack opportunities. You should also read all the assorted documentation and be sure to keep up with breaking changes involving this.call() in newer versions of Solidity, as most versions have different implementation details and nuances (0.5.0 has breaking changes). Good luck with your encoding function call journey. May it be secure and bug-free!