Contract ABI Specification
- Basic Design
- Function Selector
- Argument Encoding
TYPES
- Design criteria for the Encoding
- Formal Specification of the Encoding
- Function Selector and Argument Encoding
- Examples
- Use of Dynamic Types
- Events
JSON
- Strict Encoding Mode
- Non Standard Packed Mode
- Encoding of Indexed Parameter
Contract ABI Specification
Basic Design
The Contract Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction. Data is encoded according to its type, as described in this specification. The encoding is not self describing and thus requires a schema in order to decode.
We assume the interface functions of a contract are strongly typed, known at compilation time and static. We assume that all contracts will have the interface definitions of any contracts they call available at compile-time.
This specification does not address contracts whose interface is dynamic or otherwise known only at run-time.
Function Selector
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 (SHA-3) 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.
Note
The return type of a function is not part of this signature. In Solidity’s function overloading return types are not considered. The reason is to keep function call resolution context-independent. The JSON description of the ABI however contains both inputs and outputs.
Argument Encoding
Starting from the fifth byte, the encoded arguments follow. This encoding is also used in other places, e.g. the return values and also event arguments are encoded in the same way, without the four bytes specifying the function.
Types
The following elementary types exist:
uint<M>
: unsigned integer type ofM
bits,0 < M <= 256
,M % 8 == 0
. e.g.uint32
,uint8
,uint256
.int<M>
: two’s complement signed integer type ofM
bits,0 < M <= 256
,M % 8 == 0
.address
: equivalent touint160
, except for the assumed interpretation and language typing. For computing the function selector,address
is used.uint
,int
: synonyms foruint256
,int256
respectively. For computing the function selector,uint256
andint256
have to be used.bool
: equivalent touint8
restricted to the values 0 and 1. For computing the function selector,bool
is used.fixed<M>x<N>
: signed fixed-point decimal number ofM
bits,8 <= M <= 256
,M % 8 ==0
, and0 < N <= 80
, which denotes the valuev
asv / (10 ** N)
.ufixed<M>x<N>
: unsigned variant offixed<M>x<N>
.fixed
,ufixed
: synonyms forfixed128x18
,ufixed128x18
respectively. For computing the function selector,fixed128x18
andufixed128x18
have to be used.bytes<M>
: binary type ofM
bytes,0 < M <= 32
.function
: an address (20 bytes) followed by a function selector (4 bytes). Encoded identical tobytes24
.
The following (fixed-size) array type exists:
<type>[M]
: a fixed-length array ofM
elements,M >= 0
, of the given type.
The following non-fixed-size types exist:
bytes
: dynamic sized byte sequence.string
: dynamic sized unicode string assumed to be UTF-8 encoded.<type>[]
: a variable-length array of elements of the given type.
Types can be combined to a tuple by enclosing them inside parentheses, separated by commas:
(T1,T2,...,Tn)
: tuple consisting of the typesT1
, …,Tn
,n >= 0
It is possible to form tuples of tuples, arrays of tuples and so on. It is also possible to form zero-tuples (where n == 0
).
Mapping Solidity to ABI types
Solidity supports all the types presented above with the same names with the exception of tuples. On the other hand, some Solidity types are not supported by the ABI. The following table shows on the left column Solidity types that are not part of the ABI, and on the right column the ABI types that represent them.
Solidity | ABI |
---|---|
address payable | address |
contract | address |
enum | smallest uint type that is large enough to hold all valuesFor example, an enum of 255 values or less is mapped to uint8 and an enum of 256 values is mapped to uint16 . |
struct | tuple |
Design Criteria for the Encoding
The encoding is designed to have the following properties, which are especially useful if some arguments are nested arrays:
- The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve
a_i[k][l][r]
. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case. - The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative “addresses”.
Formal Specification of the Encoding
We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block.
Definition: The following types are called “dynamic”:
bytes
string
T[]
for anyT
T[k]
for any dynamicT
and anyk >= 0
(T1,...,Tk)
ifTi
is dynamic for some1 <= i <= k
All other types are called “static”.
Definition: len(a)
is the number of bytes in a binary string a
. The type of len(a)
is assumed to be uint256
.
We define enc
, the actual encoding, as a mapping of values of the ABI types to binary strings such that len(enc(X))
depends on the value of X
if and only if the type of X
is dynamic.
Definition: For any ABI value X
, we recursively define enc(X)
, depending on the type of X
being
(T1,...,Tk)
fork >= 0
and any typesT1
, …,Tk
enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))
whereX = (X(1), ..., X(k))
andhead
andtail
are defined forTi
as follows:ifTi
is static:head(X(i)) = enc(X(i))
andtail(X(i)) = ""
(the empty string)otherwise, i.e. ifTi
is dynamic:head(X(i)) = enc(len( head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)) ))
tail(X(i)) = enc(X(i))
Note that in the dynamic case,head(X(i))
is well-defined since the lengths of the head parts only depend on the types and not the values. The value ofhead(X(i))
is the offset of the beginning oftail(X(i))
relative to the start ofenc(X)
.T[k]
for anyT
andk
:enc(X) = enc((X[0], ..., X[k-1]))
i.e. it is encoded as if it were a tuple withk
elements of the same type.T[]
whereX
hask
elements (k
is assumed to be of typeuint256
):enc(X) = enc(k) enc([X[0], ..., X[k-1]])
i.e. it is encoded as if it were an array of static sizek
, prefixed with the number of elements.bytes
, of lengthk
(which is assumed to be of typeuint256
):enc(X) = enc(k) pad_right(X)
, i.e. the number of bytes is encoded as auint256
followed by the actual value ofX
as a byte sequence, followed by the minimum number of zero-bytes such thatlen(enc(X))
is a multiple of 32.string
:enc(X) = enc(enc_utf8(X))
, i.e.X
is utf-8 encoded and this value is interpreted as ofbytes
type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters.uint<M>
:enc(X)
is the big-endian encoding ofX
, padded on the higher-order (left) side with zero-bytes such that the length is 32 bytes.address
: as in theuint160
caseint<M>
:enc(X)
is the big-endian two’s complement encoding ofX
, padded on the higher-order (left) side with0xff
for negativeX
and with zero bytes for positiveX
such that the length is 32 bytes.bool
: as in theuint8
case, where1
is used fortrue
and0
forfalse
fixed<M>x<N>
:enc(X)
isenc(X * 10**N)
whereX * 10**N
is interpreted as aint256
.fixed
: as in thefixed128x18
caseufixed<M>x<N>
:enc(X)
isenc(X * 10**N)
whereX * 10**N
is interpreted as auint256
.ufixed
: as in theufixed128x18
casebytes<M>
:enc(X)
is the sequence of bytes inX
padded with trailing zero-bytes to a length of 32 bytes.
Note that for any X
, len(enc(X))
is a multiple of 32.
Function Selector and Argument Encoding
All in all, a call to the function f
with parameters a_1, ..., a_n
is encoded asfunction_selector(f) enc((a_1, ..., a_n))
and the return values v_1, ..., v_k
of f
are encoded asenc((v_1, ..., v_k))
i.e. the values are combined into a tuple and encoded.
Examples
Given the contract:
pragma solidity >=0.4.16 <0.7.0; 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 {} }
Thus for our Foo
example if we wanted to call baz
with the parameters 69
and true
, we would pass 68 bytes total, which can be broken down into:
0xcdcd77c0
: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signaturebaz(uint32,bool)
.0x0000000000000000000000000000000000000000000000000000000000000045
: the first parameter, a uint32 value69
padded to 32 bytes0x0000000000000000000000000000000000000000000000000000000000000001
: the second parameter – booleantrue
, padded to 32 bytes
In total:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
It returns a single bool
. If, for example, it were to return false
, its output would be the single byte array 0x0000000000000000000000000000000000000000000000000000000000000000
, a single bool.
If we wanted to call bar
with the argument ["abc", "def"]
, we would pass 68 bytes total, broken down into:
0xfce353f6
: the Method ID. This is derived from the signaturebar(bytes3[2])
.0x6162630000000000000000000000000000000000000000000000000000000000
: the first part of the first parameter, abytes3
value"abc"
(left-aligned).0x6465660000000000000000000000000000000000000000000000000000000000
: the second part of the first parameter, abytes3
value"def"
(left-aligned).
In total:
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
If we wanted to call sam
with the arguments "dave"
, true
and [1,2,3]
, we would pass 292 bytes total, broken down into:
0xa5643bf2
: the Method ID. This is derived from the signaturesam(bytes,bool,uint256[])
. Note thatuint
is replaced with its canonical representationuint256
.0x0000000000000000000000000000000000000000000000000000000000000060
: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case,0x60
.0x0000000000000000000000000000000000000000000000000000000000000001
: the second parameter: boolean true.0x00000000000000000000000000000000000000000000000000000000000000a0
: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case,0xa0
.0x0000000000000000000000000000000000000000000000000000000000000004
: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4.0x6461766500000000000000000000000000000000000000000000000000000000
: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of"dave"
, padded on the right to 32 bytes.0x0000000000000000000000000000000000000000000000000000000000000003
: the data part of the third argument, it starts with the length of the array in elements, in this case, 3.0x0000000000000000000000000000000000000000000000000000000000000001
: the first entry of the third parameter.0x0000000000000000000000000000000000000000000000000000000000000002
: the second entry of the third parameter.0x0000000000000000000000000000000000000000000000000000000000000003
: the third entry of the third parameter.
In total:
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
Use of Dynamic Types
A call to a function with the signature f(uint,uint32[],bytes10,bytes)
with values (0x123, [0x456, 0x789], "1234567890", "Hello, world!")
is encoded in the following way:
We take the first four bytes of sha3("f(uint256,uint32[],bytes10,bytes)")
, i.e. 0x8be65246
. Then we encode the head parts of all four arguments. For the static types uint256
and bytes10
, these are directly the values we want to pass, whereas for the dynamic types uint32[]
and bytes
, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
0x0000000000000000000000000000000000000000000000000000000000000123
(0x123
padded to 32 bytes)0x0000000000000000000000000000000000000000000000000000000000000080
(offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part)0x3132333435363738393000000000000000000000000000000000000000000000
("1234567890"
padded to 32 bytes on the right)0x00000000000000000000000000000000000000000000000000000000000000e0
(offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4*32 + 3*32 (see below))
After this, the data part of the first dynamic argument, [0x456, 0x789]
follows:
0x0000000000000000000000000000000000000000000000000000000000000002
(number of elements of the array, 2)0x0000000000000000000000000000000000000000000000000000000000000456
(first element)0x0000000000000000000000000000000000000000000000000000000000000789
(second element)
Finally, we encode the data part of the second dynamic argument, "Hello, world!"
:
0x000000000000000000000000000000000000000000000000000000000000000d
(number of elements (bytes in this case): 13)0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000
("Hello, world!"
padded to 32 bytes on the right)
All together, the encoding is (newline after function selector and each 32-bytes for clarity):
0x8be65246 0000000000000000000000000000000000000000000000000000000000000123 0000000000000000000000000000000000000000000000000000000000000080 3132333435363738393000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000e0 0000000000000000000000000000000000000000000000000000000000000002 0000000000000000000000000000000000000000000000000000000000000456 0000000000000000000000000000000000000000000000000000000000000789 000000000000000000000000000000000000000000000000000000000000000d 48656c6c6f2c20776f726c642100000000000000000000000000000000000000
Let us apply the same principle to encode the data for a function with a signature g(uint[][],string[])
with values ([[1, 2], [3]], ["one", "two", "three"])
but start from the most atomic parts of the encoding:
First we encode the length and data of the first embedded dynamic array [1, 2]
of the first root array [[1, 2], [3]]
:
0x0000000000000000000000000000000000000000000000000000000000000002
(number of elements in the first array, 2; the elements themselves are1
and2
)0x0000000000000000000000000000000000000000000000000000000000000001
(first element)0x0000000000000000000000000000000000000000000000000000000000000002
(second element)
Then we encode the length and data of the second embedded dynamic array [3]
of the first root array [[1, 2], [3]]
:
0x0000000000000000000000000000000000000000000000000000000000000001
(number of elements in the second array, 1; the element is3
)0x0000000000000000000000000000000000000000000000000000000000000003
(first element)
Then we need to find the offsets a
and b
for their respective dynamic arrays [1, 2]
and [3]
. To calculate the offsets we can take a look at the encoded data of the first root array [[1, 2], [3]]
enumerating each line in the encoding:
0 - a - offset of [1, 2] 1 - b - offset of [3] 2 - 0000000000000000000000000000000000000000000000000000000000000002 - count for [1, 2] 3 - 0000000000000000000000000000000000000000000000000000000000000001 - encoding of 1 4 - 0000000000000000000000000000000000000000000000000000000000000002 - encoding of 2 5 - 0000000000000000000000000000000000000000000000000000000000000001 - count for [3] 6 - 0000000000000000000000000000000000000000000000000000000000000003 - encoding of 3
Offset a
points to the start of the content of the array [1, 2]
which is line 2 (64 bytes); thus a = 0x0000000000000000000000000000000000000000000000000000000000000040
.
Offset b
points to the start of the content of the array [3]
which is line 5 (160 bytes); thus b = 0x00000000000000000000000000000000000000000000000000000000000000a0
.
Then we encode the embedded strings of the second root array:
0x0000000000000000000000000000000000000000000000000000000000000003
(number of characters in word"one"
)0x6f6e650000000000000000000000000000000000000000000000000000000000
(utf8 representation of word"one"
)0x0000000000000000000000000000000000000000000000000000000000000003
(number of characters in word"two"
)0x74776f0000000000000000000000000000000000000000000000000000000000
(utf8 representation of word"two"
)0x0000000000000000000000000000000000000000000000000000000000000005
(number of characters in word"three"
)0x7468726565000000000000000000000000000000000000000000000000000000
(utf8 representation of word"three"
)
In parallel to the first root array, since strings are dynamic elements we need to find their offsets c
, d
and e
:
0 - c - offset for "one" 1 - d - offset for "two" 2 - e - offset for "three" 3 - 0000000000000000000000000000000000000000000000000000000000000003 - count for "one" 4 - 6f6e650000000000000000000000000000000000000000000000000000000000 - encoding of "one" 5 - 0000000000000000000000000000000000000000000000000000000000000003 - count for "two" 6 - 74776f0000000000000000000000000000000000000000000000000000000000 - encoding of "two" 7 - 0000000000000000000000000000000000000000000000000000000000000005 - count for "three" 8 - 7468726565000000000000000000000000000000000000000000000000000000 - encoding of "three"
Offset c
points to the start of the content of the string "one"
which is line 3 (96 bytes); thus c = 0x0000000000000000000000000000000000000000000000000000000000000060
.
Offset d
points to the start of the content of the string "two"
which is line 5 (160 bytes); thus d = 0x00000000000000000000000000000000000000000000000000000000000000a0
.
Offset e
points to the start of the content of the string "three"
which is line 7 (224 bytes); thus e = 0x00000000000000000000000000000000000000000000000000000000000000e0
.
Note that the encodings of the embedded elements of the root arrays are not dependent on each other and have the same encodings for a function with a signature g(string[],uint[][])
.
Then we encode the length of the first root array:
0x0000000000000000000000000000000000000000000000000000000000000002
(number of elements in the first root array, 2; the elements themselves are[1, 2]
and[3]
)
Then we encode the length of the second root array:
0x0000000000000000000000000000000000000000000000000000000000000003
(number of strings in the second root array, 3; the strings themselves are"one"
,"two"
and"three"
)
Finally we find the offsets f
and g
for their respective root dynamic arrays [[1, 2], [3]]
and ["one", "two", "three"]
, and assemble parts in the correct order:
0x2289b18c - function signature 0 - f - offset of [[1, 2], [3]] 1 - g - offset of ["one", "two", "three"] 2 - 0000000000000000000000000000000000000000000000000000000000000002 - count for [[1, 2], [3]] 3 - 0000000000000000000000000000000000000000000000000000000000000040 - offset of [1, 2] 4 - 00000000000000000000000000000000000000000000000000000000000000a0 - offset of [3] 5 - 0000000000000000000000000000000000000000000000000000000000000002 - count for [1, 2] 6 - 0000000000000000000000000000000000000000000000000000000000000001 - encoding of 1 7 - 0000000000000000000000000000000000000000000000000000000000000002 - encoding of 2 8 - 0000000000000000000000000000000000000000000000000000000000000001 - count for [3] 9 - 0000000000000000000000000000000000000000000000000000000000000003 - encoding of 3 10 - 0000000000000000000000000000000000000000000000000000000000000003 - count for ["one", "two", "three"] 11 - 0000000000000000000000000000000000000000000000000000000000000060 - offset for "one" 12 - 00000000000000000000000000000000000000000000000000000000000000a0 - offset for "two" 13 - 00000000000000000000000000000000000000000000000000000000000000e0 - offset for "three" 14 - 0000000000000000000000000000000000000000000000000000000000000003 - count for "one" 15 - 6f6e650000000000000000000000000000000000000000000000000000000000 - encoding of "one" 16 - 0000000000000000000000000000000000000000000000000000000000000003 - count for "two" 17 - 74776f0000000000000000000000000000000000000000000000000000000000 - encoding of "two" 18 - 0000000000000000000000000000000000000000000000000000000000000005 - count for "three" 19 - 7468726565000000000000000000000000000000000000000000000000000000 - encoding of "three"
Offset f
points to the start of the content of the array [[1, 2], [3]]
which is line 2 (64 bytes); thus f = 0x0000000000000000000000000000000000000000000000000000000000000040
.
Offset g
points to the start of the content of the array ["one", "two", "three"]
which is line 10 (320 bytes); thus g = 0x0000000000000000000000000000000000000000000000000000000000000140
.
Events
Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract’s address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure.
Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the event signature to form the topics of the log entry. Those which are not indexed form the byte array of the event.
In effect, a log entry using this ABI is described as:
address
: the address of the contract (intrinsically provided by Ethereum);topics[0]
:keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")
(canonical_type_of
is a function that simply returns the canonical type of a given argument, e.g. foruint indexed foo
, it would returnuint256
). If the event is declared asanonymous
thetopics[0]
is not generated;topics[n]
:abi_encode(EVENT_INDEXED_ARGS[n - 1])
(EVENT_INDEXED_ARGS
is the series ofEVENT_ARGS
that are indexed);data
: ABI encoding ofEVENT_NON_INDEXED_ARGS
(EVENT_NON_INDEXED_ARGS
is the series ofEVENT_ARGS
that are not indexed,abi_encode
is the ABI encoding function used for returning a series of typed values from a function, as described above).
For all types of length at most 32 bytes, the EVENT_INDEXED_ARGS
array contains the value directly, padded or sign-extended (for signed integers) to 32 bytes, just as for regular ABI encoding. However, for all “complex” types or types of dynamic length, including all arrays, string
, bytes
and structs, EVENT_INDEXED_ARGS
will contain the Keccak hash of a special in-place encoded value (see Encoding of Indexed Event Parameters), rather than the encoded value directly. This allows applications to efficiently query for values of dynamic-length types (by setting the hash of the encoded value as the topic), but leaves applications unable to decode indexed values they have not queried for. For dynamic-length types, application developers face a trade-off between fast search for predetermined values (if the argument is indexed) and legibility of arbitrary values (which requires that the arguments not be indexed). Developers may overcome this tradeoff and achieve both efficient search and arbitrary legibility by defining events with two arguments — one indexed, one not — intended to hold the same value.
JSON
The JSON format for a contract’s interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields:
type
:"function"
,"constructor"
, or"fallback"
(the unnamed “default” function).name
: the name of the function.inputs
: an array of objects, each of which contains:name
: the name of the parameter.type
: the canonical type of the parameter (more below).components
: used for tuple types (more below).
outputs
: an array of objects similar toinputs
.stateMutability
: a string with one of the following values:pure
(specified to not read blockchain state),view
(specified to not modify the blockchain state),nonpayable
(function does not accept Ether) andpayable
(function accepts Ether).payable
:true
if function accepts Ether,false
otherwise.constant
:true
if function is eitherpure
orview
,false
otherwise.
type
can be omitted, defaulting to "function"
, likewise payable
and constant
can be omitted, both defaulting to false
.
Constructor and fallback function never have name
or outputs
. Fallback function doesn’t have inputs
either.
Warning
The fields constant
and payable
are deprecated and will be removed in the future. Instead, the stateMutability
field can be used to determine the same properties.
Note
Sending non-zero Ether to non-payable function will revert the transaction.
An event description is a JSON object with fairly similar fields:
type
: always"event"
name
: the name of the event.inputs
: an array of objects, each of which contains:name
: the name of the parameter.type
: the canonical type of the parameter (more below).components
: used for tuple types (more below).indexed
:true
if the field is part of the log’s topics,false
if it one of the log’s data segment.
anonymous
:true
if the event was declared asanonymous
.
For example,
pragma solidity >=0.5.0 <0.7.0; contract Test { constructor() public { b = hex"12345678901234567890123456789012"; } event Event(uint indexed a, bytes32 b); event Event2(uint indexed a, bytes32 b); function foo(uint a) public { emit Event(a, b); } bytes32 b; }
would result in the JSON:
[{ "type":"event", "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], "name":"Event" }, { "type":"event", "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], "name":"Event2" }, { "type":"function", "inputs": [{"name":"a","type":"uint256"}], "name":"foo", "outputs": [] }]
Handling tuple types
Despite that names are intentionally not part of the ABI encoding they do make a lot of sense to be included in the JSON to enable displaying it to the end user. The structure is nested in the following way:
An object with members name
, type
and potentially components
describes a typed variable. The canonical type is determined until a tuple type is reached and the string description up to that point is stored in type
prefix with the word tuple
, i.e. it will be tuple
followed by a sequence of []
and [k]
with integers k
. The components of the tuple are then stored in the member components
, which is of array type and has the same structure as the top-level object except that indexed
is not allowed there.
As an example, the code
pragma solidity >=0.4.19 <0.7.0; pragma experimental ABIEncoderV2; contract Test { struct S { uint a; uint[] b; T[] c; } struct T { uint x; uint y; } function f(S memory s, T memory t, uint a) public; function g() public returns (S memory s, T memory t, uint a); }
would result in the JSON:
[ { "name": "f", "type": "function", "inputs": [ { "name": "s", "type": "tuple", "components": [ { "name": "a", "type": "uint256" }, { "name": "b", "type": "uint256[]" }, { "name": "c", "type": "tuple[]", "components": [ { "name": "x", "type": "uint256" }, { "name": "y", "type": "uint256" } ] } ] }, { "name": "t", "type": "tuple", "components": [ { "name": "x", "type": "uint256" }, { "name": "y", "type": "uint256" } ] }, { "name": "a", "type": "uint256" } ], "outputs": [] } ]
Strict Encoding Mode
Strict encoding mode is the mode that leads to exactly the same encoding as defined in the formal specification above. This means offsets have to be as small as possible while still not creating overlaps in the data areas and thus no gaps are allowed.
Usually, ABI decoders are written in a straightforward way just following offset pointers, but some decoders might enforce strict mode. The Solidity ABI decoder currently does not enforce strict mode, but the encoder always creates data in strict mode.
Non-standard Packed Mode
Through abi.encodePacked()
, Solidity supports a non-standard packed mode where:
- types shorter than 32 bytes are neither zero padded nor sign extended and
- dynamic types are encoded in-place and without the length.
- array elements are padded, but still encoded in-place
Furthermore, structs as well as nested arrays are not supported.
As an example, the encoding of int16(-1), bytes1(0x42), uint16(0x03), string("Hello, world!")
results in:
0xffff42000348656c6c6f2c20776f726c6421 ^^^^ int16(-1) ^^ bytes1(0x42) ^^^^ uint16(0x03) ^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field
More specifically:
- During the encoding, everything is encoded in-place. This means that there is no distinction between head and tail, as in the ABI encoding, and the length of an array is not encoded.
- The direct arguments of
abi.encodePacked
are encoded without padding, as long as they are not arrays (orstring
orbytes
). - The encoding of an array is the concatenation of the encoding of its elements with padding.
- Dynamically-sized types like
string
,bytes
oruint[]
are encoded without their length field. - The encoding of
string
orbytes
does not apply padding at the end unless it is part of an array or struct (then it is padded to a multiple of 32 bytes).
In general, the encoding is ambiguous as soon as there are two dynamically-sized elements, because of the missing length field.
If padding is needed, explicit type conversions can be used: abi.encodePacked(uint16(0x12)) == hex"0012"
.
Since packed encoding is not used when calling functions, there is no special support for prepending a function selector. Since the encoding is ambiguous, there is no decoding function.
Warning
If you use keccak256(abi.encodePacked(a, b))
and both a
and b
are dynamic types, it is easy to craft collisions in the hash value by moving parts of a
into b
and vice-versa. More specifically, abi.encodePacked("a", "bc") == abi.encodePacked("ab", "c")
. If you use abi.encodePacked
for signatures, authentication or data integrity, make sure to always use the same types and check that at most one of them is dynamic. Unless there is a compelling reason, abi.encode
should be preferred.
Encoding of Indexed Event Parameters
Indexed event parameters that are not value types, i.e. arrays and structs are not stored directly but instead a keccak256-hash of an encoding is stored. This encoding is defined as follows:
- the encoding of a
bytes
andstring
value is just the string contents without any padding or length prefix. - the encoding of a struct is the concatenation of the encoding of its members, always padded to a multiple of 32 bytes (even
bytes
andstring
). - the encoding of an array (both dynamically- and statically-sized) is the concatenation of the encoding of its elements, always padded to a multiple of 32 bytes (even
bytes
andstring
) and without any length prefix
In the above, as usual, a negative number is padded by sign extension and not zero padded. bytesNN
types are padded on the right while uintNN
/ intNN
are padded on the left.
Warning: The encoding of a struct is ambiguous if it contains more than one dynamically-sized array. Because of that, always re-check the event data and do not rely on the search result based on the indexed parameters alone.
[ad_2]
This article has been published from the source link without modifications to the text. Only the headline has been changed.