Whether you’re aware of it or not, Application Binary Interfaces (ABIs) are used everywhere within the inner workings smart contracts and typical Ethereum transactions. As a Solidity / EVM developer, you will inevitably run into strange and unusual scenarios where solid knowledge of ABIs will guide you to solving the obstacles you face.
In short, ABIs are used any time a contract needs to respond to a function call.
As you can imagine, this can be very common. Let’s explore the common situations where ABIs are used:
Any time you call a contract function on-chain using your wallet, you are sending an ABI-encoded function call.
A common example of using ABI-encoded transaction data is making a trade on Uniswap. Let’s say you want to swap ETH for USDC. You would:
Upon clicking that button, the page’s JavaScript prepares an ABI encoding of the function selector and arguments, and prompts you (via e.g. MetaMask) to create and send a transaction with that ABI encoded data as the transaction’s data
field, to be sent to the contract you’re using to trade.
To continue this example, let’s take a look at what this data looks like, and in a future chapter we will learn the details of the encoding algorithm itself.
Below is a screenshot of what the MetaMask prompt could look like after clicking the “Confirm Swap” button:
Note the HEX
tab – that’s what we want to look at next:
See that HEX DATA: 420 BYTES
field? This is what ABI encoded data looks like! It may look scary now, but after completing the next few chapters, you will have a solid understanding of what this data is really doing.
The HEX DATA
we just saw above is the exact same format of data that contracts use to interact with each other on-chain.
Concretely, any time you see an external function call in Solidity:
uint result = someExternalContract.foo(10);
This call compiles down to code that:
someExternalContract
address using the CALL opcode (more on that opcode later).someExternalContract
address then interprets that call, ABI-decoding the incoming message, running its own logic, and eventually ABI-encoding a return value.Here is some Solidity code that is roughly equivalent to the above code snippet:
// This Solidity code:
//
// uint result = someExternalContract.foo(10);
//
// is roughly equivalent to the following:
//
(bool success, bytes memory returnData)
= someExternalContract.call(abi.encodeWithSignature('foo(uint256)', [10]));
require(success, "foo failed");
(uint result) = abi.decode(returnData, (uint));
There are more detail we could cover, but we’ll stop here and move on since this is only the introductory “why should I learn this?” lesson 🙂
Both Hardhat and frontend applications use ethers.js or something similar. This library allows you to write JavaScript code that looks like this:
await greeterContract.setGreeting("Hello!");
This is nice, but how does ethers.js know what this function should do? It’s because of the contract’s ABI declarations that get generated during compilation.
For example, when you compile Solidity with Hardhat, Hardhat generates JSON files that look like the following:
In this example:
artifacts/contracts/Greeter.sol/Greeter.json
abi
array to generate JavaScript functions that can be used like the await greeterContract.setGreeting("Hello!")
snippet above.Lastly, understanding ABIs will open the gate to fully understanding:
externalContract.call()
. ABIs are part of the low-level concept that the Solidity docs refers to.