Solidity encoding built-in functions explanation

Al-Qa'qa'
Coinmonks
4 min readAug 29, 2023

--

binary-code | photo by rawpixel.com on freepik

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 by abi.encode.
  • You can encode function calls to be called from another contract using abi.encodeWithSignature or abi.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.

--

--

Al-Qa'qa'
Coinmonks

Smart Contract Auditor | Smart Contract Security Researcher