Solidity encoding built-in functions explanation
In Solidity programming language, there is some function that can encode data into low-level (bytes), where some solutions can only be achieved by encoding high-level syntaxes into low-level syntaxes, such as calling a function from another contract, concatenating two strings (in older solidity versions), and more other things.
Solidity provides a lot of built-in encoding functions each for a given purpose, we will try to illustrate and discuss most of these encoding functions, and their usage.
abi.encode
abi.encode
is used to encode multiple values into a single-byte array.
uint256 public number = 10;
function encode(uint256 _number)
public
pure
returns (bytes memory)
{
return abi.encode(_number);
// 0x000000000000000000000000000000000000000000000000000000000000000a
}
In the following code, the returned value will be an array of bytes that represents the number 10
.
NOTE: hex format of number 10
is 0xa
.
You can encode more than one value using abi.encode
.
uint256 public number = 10;
address public addr = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
bool public boolean = true;
function encode(uint256 _number, address _addr, bool _boolean)
public
pure
returns (bytes memory)
{
return abi.encode(_number,_addr, _boolean);
// 0x
// 000000000000000000000000000000000000000000000000000000000000000a -> number encoded
// 000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2 -> address encoded
// 0000000000000000000000000000000000000000000000000000000000000001 -> boolean true value encoded
}
As you can see in this example we encoded 3 different values, and encode function returns bytes that represent these three values.
abi.encodePacked
abi.encodePacked
is similar to encode
function, but instead of encoding element values only, it reduced the unnecessary bytes that are not needed.
For example, the uint256
type is encoded to 32 bytes (this is the default encoding in the encode
function for static types), when you encode a boolean
value, you don’t need 32 bytes to represent the encoding of boolean values, one byte will fit, and this is what abi.encodePacked
do.
abi.encodePacked
is used to pack the encoded data to reduce encoded bytes array length.
uint256 public number = 10;
address public addr = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
bool public boolean = true;
function encodePacked(
uint256 _number,
address _addr,
bool _boolean
) public pure returns (bytes memory) {
return abi.encodePacked(_number, _addr, _boolean);
// 0x
// 000000000000000000000000000000000000000000000000000000000000000a -> uint256 number encoding
// ab8483f64d9c6d1ecf9b849ae677dd3315835cb2 -> address encoding
// 01 -> boolean true value encoded
}
As you can see, abi.encodePacked
encoded uint256 into 32 bytes, encoded the address into 20 bytes and encoded the boolean into just one byte.
NOTE: One byte (8 bits) is represented by 2 hex (4-bit) digits
abi.encodeWithSignature
abi.encodeWithSignature
is used to encode function names and parameters, in order to be called by another contract, or to be called indirectly from the same contract.
Function signature is the function name + function parameters types passed to it as arguments in a string format.
We will take a look at how to use abi.encodeWithSignature
to encode function calls.
// This function returns the name passed to it (its the function that will be encoded)
function printName(string memory _name) public pure returns (string memory) {
return _name;
}
// This function encoded `printName` function with the parameter `_name` equal to Al-Qa'qa'
function encodeWithSignature() public pure returns (bytes memory) {
return abi.encodeWithSignature("printName(string)", "Al-Qa'qa'");
}
// Calling `printName` function using the signature returned from `encodeWithSignature` function
function callPrintName() public returns(bool, bytes memory) {
(bool success, bytes memory data) = address(this).call(encodeWithSignature());
return (success, data);
}
// The encoded value of `Al-Qa'qa'` to check that the function returned value is correct
function encodeName() public pure returns (bytes memory) {
return abi.encode("Al-Qa'qa'");
}
In the previous example, we made a function printName
, which has one argument string
, and it returns this string
argument value.
encodeWithSignature
returns the encoding of the printName
function, with the _name
parameter value equals Al-Qa'qa'
.
callPrintName
function calls printName
function using the signature that is returned from encodeWithSignature
function.
When you call callPrintName
function, it will return two values:
- Calling status: It should be
true
if the calling happened successfully. - data: the encoded returned data from
printName
function.
Returned results after firing callPrintName
function.
success: true
data: 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009416c2d5161277161270000000000000000000000000000000000000000000000
When you fire encodeName
function to encode Al-Qa'qa'
string variable, it should give the same result as the data returned from callPrintName
function.
Returned results after firing encodeName
function.
0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009416c2d5161277161270000000000000000000000000000000000000000000000
As you can see, the values are the same, which means that calling the function occurred successfully without any problems.
abi.encodeWithSelector
abi.encodeWithSelector
is similar to abi.encodeWithSignature
.
Instead of using a function signature to encode, we will use a function selector to encode our function.
- Function selector is a unique identifier that is generated from the function name, and parameter types.
- Function selector is the first 4 bytes of the
keccak-256
hash of the function’s canonical signature.
You can learn more about function selectors: What is a function selector
We will see how to encode functions using abi.encodeWithSelector
instead of using abi.encodeWithSignature
.
// This function encoded `printName` function with the parameter `_name` equal to Al-Qa'qa'
function encodeWithSelector() public pure returns (bytes memory) {
return
abi.encodeWithSelector(
bytes4(keccak256("printName(string)")), // function selector
"Al-Qa'qa'"
);
}
As you can see from the previous example:
- we used
abi.encodeWithSelector
function. - We got a function selector using the
keccak256
hashing function, taking the first 4 bytes of the result.
Calling the function printName
will be the same as abi.encodeWithSignature
, so we will escape calling to not repeat ourselves.
Recap
- Solidity has different built-in encoding functions
abi.encode
is used to encode data passed to it.abi.encodePacked
is used to encode data passed to it, removing the unnecessary bytes, that are used byabi.encode
.- You can encode function calls to be called from another contract using
abi.encodeWithSignature
orabi.encodeWithSelector
. abi.encodeWithSignature
is used to encode function calls using function signature.abi.encodeWithSelector
is used to encode function calls using a function selector.- Function signature is the function name + parameters types.
- Function selector is the first 4 bytes of
keccak256
hash of the function signature.