Interacting with Smart Contracts
The Ethereum blockchain was designed from ground-up as a general-purpose blockchain capable of running smart contracts, as explained in the original whitepaper. Smart contracts are pieces of code that are stored on the blockchain and can be executed by the Ethereum Virtual Machine (EVM).
A smart contract exists at an Ethereum address and pieces of code are executed when a smart contract method is called, either by an external user or another smart contract. Of course, external users and other smart contracts need some knowledge of a smart contract’s interface, such as a way to identify a method and its parameters, in order to correctly interact with it. This interaction is facilitated by the Ethereum Application Binary Interface (ABI).
APIs vs ABIs
In order to understand how the Ethereum ABI works, it is important to understand how smart contract code is stored and executed.
The EVM provides a comprehensive instruction set, consisting of so-called opcodes. Opcodes are a low-level concept, akin to a processor’s instruction set. Programmers generally do not use opcodes directly but rely on high-level programming languages, such as Solidity, as a more manageable abstraction. Solidity code is compiled into byte code, a binary format that is stored on the blockchain.
When programmers interact with other programs in the form of source code, they can use an Application Program Interface (API), which is essentially a programmatic description of the code’s interface in the actual programming language or a human-readable form. However, with compiled code human-readable interfaces disappear and smart contract interactions have to be translated into a binary description that can be interpreted by the EVM. This process is called ABI encoding.
ABI Encoding Smart Contract Calls
ABI encoding is usually automated by tools that form part of the compiler or other software, such as wallets capable of interacting with smart contracts. The so-called ABI encoder requires at least a description of the contract’s interface including function names and parameter types. A common way to provide such a description is in a standardized JSON file. For example, the official ABI description of a contract implementing an ERC-20 token can be found here.
In order to call a method of a contract, a byte string in a specific format needs to be constructed. To make the call, this string is sent in the data field of a transaction destined to the smart contract’s address.
The above image illustrates how function calls are ABI encoded. The first four bytes of the ABI encoded function call is a special field called the function selector.
These four bytes are calculated from the function signature. The function signature is defined as a string consisting of the function name, followed by the types of the parameters in a comma-separated list in parenthesizes without any white space. This function signature is processed using Ethereum’s hash function keccak256. The top four bytes of the resulting hash value are taken as the function selector.
Let’s look at an example: Consider the standard ERC-20 method balanceOf, which can be used to obtain the token balance of a particular account. The function signature is as follows:
Calculating the keccak256 hash of this signature string produces:
Therefore, taking the top four bytes gives us the following function selector:
Of course, getting from function selectors to the actual function is computationally hard due to the one-way nature of hashing algorithms. However, a database of commonly found function selectors and their corresponding signatures is maintained here.
As shown in the above image, ABI encoding places the arguments of a function call after the function selector. To this end, it is important to realize that argument types have to correspond to the low-level types understood by the EVM. The Solidity programming language adds a larger set of types for convenience. The compiler takes care of translating these to EVM types.
The above illustration is simplified in that the function only takes arguments that can be encoded in 256-bit data fields, but the principle applies to larger arguments, such as fixed-sized arrays, as well. These will simply be encoded as multiple 256-bit fields. Smaller types, such as 8-bit integers and boolean values, are padded with leading zeros and strings are padded with trailing zeros.
Dynamic types, such as variable-sized arrays are added at the end and referenced from their position in the argument list.
The detailed documentation of Ethereum ABI encoding, including data type translation and a formal specification, can be found in the official Solidity documentation.