Learn on Mantle: Solidity Series Part 2

05/09/236 min read

Mantleby Mantle

Developers

Tutorials

Web3

Learn on Mantle: Solidity Series Part 2

In our previous article we explored how solidity handled fixed sized variables (e.g. uint256, bytes32, address). In this article, we will explore how storage is handled in dynamic data structures such as arrays and mappings. Unlike fixed size variables, dynamic variables require more complex handling in terms of how they are stored in storage slots.

Arrays

Arrays in Ethereum are dynamically-sized; each element in the array is stored in a separate storage slot. The slot assigned to the array stores the length of the array, we will define that as p. Whereas the first element of the array will be stored at keccak(p), the second element will be stored at keccak(2) + 1, and so on.

For example, if we have an array of integers called “myArray”, the storage slots would be allocated as follows:

contract myContract {
uint256 a; // Takes up slot 0
uint256 b; // Takes up slot 1
// Takes up slot 2, length of the array is stored at slot 2
// First element is stored at keccak256(2), second at keccak256(2) + 1, and so on.
uint256[] myArray;
}

We can validate that by testing it with forge. First we initialize the arrays to values [42, 43, 44], and then add a readStorageSlot function as the contracts are only able to access their own storage slots within the Ethereum Virtual Machine (EVM).

contract Contract {
uint256 a;
uint256 b;
uint256[] myArray;

constructor() {
a = 100;
b = 200;

myArray = new uint256[](3);
myArray[0] = 42;
myArray[1] = 43;
myArray[2] = 44;
}

function readStorageSlot(bytes32 slot) public view returns (uint256) {
uint256 result;
assembly {
result := sload(slot)
}
return result;
}
}

We then write a test function that logs out the output of the storage slots:

function testSlot() public {
emit log_string("Array length (slot 2)");
emit log_uint(myContract.readStorageSlot(bytes32(uint256(2))));
emit log_string("--------");
emit log_string("First element of array - keccak256(2) ");
emit log_uint(
myContract.readStorageSlot(keccak256(abi.encode(uint256(2))))
);
emit log_string("--------");
emit log_string("Second element of array - keccak256(2) + 1 ");
emit log_uint(
myContract.readStorageSlot(
bytes32((uint256(keccak256(abi.encode(uint256(2)))) + 1))
)
);
}

The output of which is:

[PASS] testSlot() (gas: 25472)
Logs:
Array length (slot 2)
3
--------
First element of array - keccak256(2)
42
--------
Second element of array - keccak256(2) + 1
43

Mappings

Mappings in Ethereum are a dynamically sized element, with a key-value data structure. Each key-value pair is stored in a separate storage slot. Unlike arrays, mappings do not have a predefined length. Instead, the keys and values are dynamically added and removed as needed.

When a new key-value pair is added to a mapping, a new storage slot is allocated to store the pair. The location of the slot is keccak(key, p) where the key is the key in the key-value data structure, and p is the slot assigned to the mapping (note that vyper handles it in reverse, and defines the slot to be keccak(p, key)). For example, if we have a mapping called “myMapping” with key-value pair 42, the storage slots would be allocated as follows:

contract Contract {
uint256 a; // Slot 0
uint256 b; // Slot 1
// Slot 2
// Element of key will be defined in keccak(key, 2)
mapping(uint256 => uint256) myMapping;

constructor() {
// Slot will be at keccak(42, 2)
myMapping[42] = 888;
}

function readStorageSlot(bytes32 slot) public view returns (uint256)
{ ... }
}

Again, we can validate that by testing it with forge. We will initialize myMapping[42] = 888 to test.

function testSlot() public {
emit log_string("Value of myMapping[42] - keccak256(42, 2) ");
emit log_uint(
myContract.readStorageSlot(keccak256(abi.encode(42, uint256(2))))
);
}

The output of which is:

[PASS] testSlot() (gas: 10662)
Logs:
Value of myMapping[42] - keccak256(42, 2)
888

Conclusion

So there you have it! Understanding Solidity’s storage slots is key to creating efficient and secure smart contracts on the Ethereum blockchain. In the next article we will explore the dreaded call and delegateCall opcode in Ethereum.


Join the Community