Examples
The example given is a simple Ethereum smart contract named Foo written in Solidity. It has three functions: bar, baz, and sam.
contract Foo {
function bar(bytes3[2] memory) public pure {}
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function sam(bytes memory, bool, uint[] memory) public pure {}
}Calling baz Function:
If we want to call the baz function with arguments 69 (a uint32) and true (a bool), the ABI encoded data would be:
Method ID (
0xcdcd77c0): The first 4 bytes of the Keccak-256 hash of the ASCII form of the signaturebaz(uint32,bool).First Parameter (
0x...45):uint32value 69, but ABI-encoded to be right-aligned within a 32-byte word.Second Parameter (
0x...01):boolvalue true, ABI-encoded as auint256(0 for false, 1 for true) and again right-aligned within a 32-byte word.
Calling bar Function:
To call the bar function with a 2-element bytes3 array ["abc", "def"], the ABI encoded data would be:
Method ID (
0xfce353f6): Derived from the signaturebar(bytes3[2]).First and Second Parameters: The
bytes3values "abc" and "def", right-aligned within their respective 32-byte words.
Calling sam Function:
For the sam function, if we use arguments "dave" (a bytes string), true (a bool), and an array [1,2,3] (a uint[] array), the ABI encoded data would be:
Method ID (
0xa5643bf2): Derived from the signaturesam(bytes,bool,uint256[]).Dynamic Type Offset for First Parameter: Points to the location of the data for the first parameter (since it's a dynamic type).
Second Parameter (
bool): As before, 0 or 1 represented as auint256.Dynamic Type Offset for Third Parameter: Points to the location of the data for the third parameter.
Data for First Parameter: Starts with the length of the byte array and then the data itself.
Data for Third Parameter: Starts with the length of the array and then each of the array elements (in this case, the numbers 1, 2, and 3).
Important Note: The way Ethereum ABI works is by distinguishing between fixed-size and dynamic-sized types. For fixed-size types, it directly encodes the values. For dynamic-sized types (like bytes or arrays), it first specifies the offset (or location) where the actual data starts, then later on in the encoded payload, you'd find the actual data.
To be proficient at understanding and constructing ABI-encoded data, it often requires practice and a solid understanding of the ABI encoding rules. Many developers use libraries or tools to help with this encoding, but understanding the underlying process can be very beneficial for debugging and advanced usage.
Encoding Steps
Static Types: For static types, you just take the value and pad it to 32 bytes if it's not already.
Example: The value
0x123foruint256is encoded as0x000...123(padded to 32 bytes).Dynamic Types: A bit trickier. For dynamic types, you don't immediately put the value. Instead, you put a "pointer" indicating where the actual value starts in the encoded data. This "pointer" is an offset in bytes from the start of the value encoding.
The Actual Data: After encoding all the function parameters, you then put the actual data of the dynamic types in the order they appear.
Detailed Breakdown
Let's break down the example given:
Function: f(uint256,uint32[],bytes10,bytes) with values (0x123, [0x456, 0x789], "1234567890", "Hello, world!").
Function Selector:
0x8be65246First Argument (Static):
0x123becomes0x000...123.Second Argument (Dynamic, Array): Instead of directly putting
[0x456, 0x789], we put a pointer where this data will later be placed. That pointer is0x80which means "skip 128 bytes from the start of this encoding to find the data for me". Why 128? Because we've already consumed 4 bytes for the function selector and will consume 32 bytes for each of the next 4 parameters (128 bytes in total).Third Argument (Static): The string "1234567890" becomes
0x313233343536373839300000...Fourth Argument (Dynamic, String): Similarly, a pointer. But note the pointer is now
0xE0, indicating where "Hello, world!" will be.Second Argument's Data:
First, the length of the array:
0x2.Elements:
0x456and0x789.
Fourth Argument's Data:
Length:
0xD(13 in decimal, because "Hello, world!" is 13 characters).The string itself.
For the next example with function g(uint256[][],string[]) and values ([[1, 2], [3]], ["one", "two", "three"]), the same principles are applied, but with an added complexity because of the nested arrays.
Key Takeaways
Ethereum uses a specific encoding mechanism to ensure that data and function calls are correctly understood and processed.
Static types are straightforward to encode.
Dynamic types involve placing pointers first and then adding the actual data later in the sequence.
Nested dynamic types, like arrays of arrays, follow the same principle but require careful calculation of where each inner array's data is placed.
Last updated