Guide to Solidity ABIs
A guide to understanding what ABIs are
Table of Contents for Guide to Solidity ABIs
When you compile a smart contract, you can export the Application Binary Interface (ABI).
This is a simple JSON document that contains the interface of all methods in a contract (including what arguments to pass in, and what the return).
It does not include the implementation of those functions.
You can use an ABI along with a deployed smart contract (or its bytecode) to easily interact with functionality.
Example of ABI from a Solidity smart contract
The following simple smart contract is listed below, and after that the ABI output.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract HelloWorld {
string greeting = "Default hello world";
function setGreeting(string calldata _newGreeting ) public {
greeting = _newGreeting;
}
function sayHi() public view returns (string memory){
return greeting;
}
}
[
{
"inputs": [],
"name": "sayHi",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_newGreeting",
"type": "string"
}
],
"name": "setGreeting",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
You can see from the ABI that there are a couple of functions to call:
sayHi
which has no inputs, so its full structure issayHi()
setGreeting
which has one input, so its structure issetGreeting(string)
.
Function signatures
Given a function name & its arguments, you can create a hash of it and the first four bytes of this are used as the function selector.
Note: if two different functions have the same first four bytes as a signature in a smart contract, there will be a compile error.
This selector is saved to a mapping of selectors to the bytecode to execute for that function.
How function selectors are generated
- they look at just the function name and types of arguments
- it then turns that into a string
uint
is turned intouint256
. There are some other conversions (see below)- no spaces are used
- return type is not used (note: they are in the ABI output though, just not used for the function selector)
- then it takes the first (left, high order in big endian) four bytes of the keccak256 hash of that signature
So you would do this in pseudo code:
let stringSignature = 'sayHi(string)';
let hash = keccak256(stringSignature);
let selector = first4Bytes(hash);
The first four bytes of the call data for a function call specifies the function to be called. It is the first (left, high-order in big-endian) four bytes of the Keccak-256 hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype without data location specifier, i.e. the function name with the parenthesised list of parameter types. Parameter types are split by a single comma - no spaces are used.
See my full guide on generating function selectors in Solidity here
data types
- uints - uint8, uint16, uint32, etc up to uint256 (always where the num of bytes is a multiple of 8
uint
is the same asuint256
, but for calculating function selectorsuint256
is used
- ints
int8
,uint16
, etc up touint256
(again where the number is a multiple of 8)int
is the same asint256
, but for calculating function selectorsint256
is used
address
, which is stored the same as auint160
bytesX
where X is from 1-32, such asbytes1
,bytes2
,bytes32
function
- a 20 bytes address followed by a function selector (4 bytes) - encoded in the same way as abytes24
There are also fixed size arrays:
<type>[M]
(e.g.uint[5]
) a fixed-length array of M elements, M >= 0, of the given type.
and dynamic sized arrays:
bytes
dynamic-sized byte sequence.string
dynamic-sized strings. Unicode, encoded in UTF8<type>[]
(e.g.uint[]
) a variable-length array of elements of the given type.
And tuples: (T1,T2,...,Tn): tuple consisting of the types T1, β¦, Tn, n >= 0
A few more not covered here but listed in the docs:
fixed<M>x<N>
: signed fixed-point decimal number of M bits, 8 <= M <= 256, M % 8 == 0, and 0 < N <= 80, which denotes the value v as v / (10 ** N).ufixed<M>x<N>
: unsigned variant of fixed<M>x<N>.fixed
,ufixed
: synonyms forfixed128x18
,ufixed128x18
respectively. For computing the function selector,fixed128x18
andufixed128x18
have to be used.
How the data types are mapped
- If your Solidity code has an argument of
uint
, the selector should actually useuint256
(setCountValue(uint256)
for example) address payable
andcontract
s are mapped to justaddress
enum
is mapped touint8
(since 0.8.0 - before that version they could have more than 256 values and were mapped to the smallest uint type to fit all values).struct
is mapped totuple
Spotted a typo or have a suggestion to make this crypto dev article better? Please let me know!
Next post
Previous post
π Solidity Auditing online quiz
Learn how to audit smart contracts by looking at some example code and trying to find the bugs
β½ Solidity Gas Optimizations Guide
How to optimize and reduce gas usage in your smart contracts in Solidity
π§ͺ Guide to testing with Foundry
Guide to adding testing for your Solidity contracts, using the Foundry and Forge tools
π Guide to UTXO
UTXO and the UTXO set (used by blockchains such as Bitcoin) explained
π Solidity Assembly Guide
Introduction guide to using assembly in your Solidity smart contracts
π¦ Ethereum EOF format explained
Information explaining what the upcoming Ethereum EOF format is all about