When crafting transaction script data, you must encode the arguments you wish to pass to the script.
These are the available types that can be encoded in the ABI:
u8
, 8 bits. u16
, 16 bits. u32
, 32 bits. u64
, 64 bits. bool
, either 0
or 1
encoded identically to u8
. b256
, arbitrary 256-bits value. address
, a 256-bit (32-byte) address. These types are encoded in-place. Here's how to encode them. We define enc(X)
the encoding of the type X
.
u<M>
where M
is either 8, 16, 32, or 64 bits.
enc(X)
is the big-endian (i.e. right-aligned) representation of X
left-padded with zero-bytes. Total length must be 8 bytes.
Note: since all integer values are unsigned, there is no need to preserve the sign when extending/padding; padding with only zeroes is sufficient._
Example:
Encoding 42
yields: 0x000000000000002a
, which is the hexadecimal representation of the decimal number 42
, right-aligned to 8 bytes.
enc(X)
is 0
if X
is false or 1
if X
is true, left-padded with zero-bytes. Total length must be 8 bytes. Similar to the u8
encoding.
Example:
Encoding true
yields:
0x0000000000000001
b256
is a fixed size bit array of length 256. Used for 256-bit hash digests and other 256-bit types. It is encoded as-is.
Example:
Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
yields:
0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
A 256-bit (32-byte) address, encoded in the same way as a b256
argument: encoded as-is.
Example:
Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
yields:
0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
An array of a certain type T
, [T; n]
, where n
is the length of the array.
Arrays in Sway have a fixed-length which is known at compile time. This means the ABI encoding for arrays also happens in-place, with no need to account for dynamic sizing.
The encoding for the array contains, in order, the encoding of each element in [T; n]
, recursively following the encoding for the type T
.
For instance, consider the function signature my_func(bool, [u64; 2])
with the values (true, [1, 2])
.
The encoding will be:
0x0000000000000001
, the true
bool encoded in-place. 0x0000000000000001
, First element of the parameter [u64; 2]
, 1
, encoded as a u64
. 0x0000000000000002
, Second element of the parameter [u64; 2]
, 2
, encoded as a u64
. The resulting encoded ABI will be:
0x000000000000000100000000000000010000000000000002
Strings have fixed length and are encoded in-place. str[n]
, where n
is the fixed-size of the string. Rather than padding the string, the encoding of the elements of the string is tightly packed. And unlike the other type encodings, the string encoding is left-aligned.
Note that all strings are encoded in UTF-8.
Example:
Encoding "Hello, World"
as a str[12]
yields:
0x48656c6c6f2c20576f726c6400000000
Note that we're padding with zeroes in order to keep it right-aligned to 8 bytes (FuelVM's word size).
Encoding ABIs that contain custom types, such as structs, is similar to encoding a set of primitive types. The encoding will proceed in the order that the inner types of a custom type are declared and recursively just like encoding any other type. This way you can encode structs with primitive types or structs with more complex types in them such as other structs, arrays, strings, and enums.
Here's an example:
struct InputStruct {
field_1: bool,
field_2: u8,
}
abi MyContract {
fn foo(a: u64);
fn bar(a: InputStruct);
} {
fn baz(a: ()) { }
}
Calling bar
with InputStruct { field_1: true, field_2: 5 }
yields:
0x
0000000000000001 // `true` encoded as a bool
0000000000000005 // `5` encoded as u8
A more complex scenario:
struct InputStruct {
field_1: bool,
field_2: [u8; 2], // An array of u8, with length 2.
}
abi MyContract {
fn foo(a: u64);
fn bar(a: InputStruct);
} {
fn baz(a: ()) { }
}
Calling bar
with InputStruct { field_1: true, field_2: [1, 2] }
yields:
0x
0000000000000001 // `true` encoded as a bool
0000000000000001 // `1` encoded as u8
0000000000000002 // `2` encoded as u8
ABI calls containing enums (sum types) are encoded similarly to structs: encode the discriminant first, then recursively encode the type of the enum variant being passed to the function being called.
enum MySumType
{
X: u32,
Y: bool,
}
abi MyContract {
fn foo(a: u64);
fn bar(a: MySumType);
} {
fn baz(a: ()) { }
}
Calling bar
with MySumType::X(42)
yields:
0x
0000000000000000 // The discriminant of the chosen enum, in this case `0`.
000000000000002a // `42` encoded as u64
If the sum type has variants of different sizes, then left padding is required.
enum MySumType
{
X: b256,
Y: u32,
}
abi MyContract {
fn foo(a: u64);
fn bar(a: MySumType);
} {
fn baz(a: ()) { }
}
Calling bar
with MySumType::Y(42)
yields:
0x
0000000000000001 // The discriminant of the chosen enum, in this case `1`.
0000000000000000 // Left padding
0000000000000000 // Left padding
0000000000000000 // Left padding
000000000000002a // `42` encoded as u64
Note that three words of padding are required because the largest variant of MySumType
is 4 words wide.
If all the variants of a sum type are of type ()
, or unit, then only the discriminant needs to be encoded.
enum MySumType
{
X: (),
Y: (),
Z: (),
}
abi MyContract {
fn foo(a: u64);
fn bar(a: MySumType);
} {
fn baz(a: ()) { }
}
Calling bar
with MySumType::Z
yields:
0x
0000000000000002 // The discriminant of the chosen enum, in this case `2`.