mapping
A typical mapping
in Solidity is accessed using the key
. There are two types of mapping
as you know, single
mapping
and nested mapping
. We will look at how to retrieve data from a single and a nested mapping
.
Single mapping
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Yul {
mapping(address => uint256) public myMap;
function getMap() public view returns (uint256) {
return myMap[msg.sender];
}
}
In the getMap
function, the msg.sender
is the key.
In Yul, mappings
take up a full slot. They cannot be packed with any other variable. And to access a mapping
key
value, the value is stored at a location calculated as the keccak256(key, slot)
. In other words, to access the
value of a key
in a mapping
, we must know the corresponding slot
of the mapping
, and the key
we want to look for.
Then, we store the key
first in the first 32 bytes of free memory, and store the slot
in the next 32 bytes of free
memory, and then hash the entire thing from the start of the first memory containing the key
to the end of the
second memory containing the slot
. This will always be a constant size of 64 bytes
, 0x40
in hexadecimals.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Yul {
uint64 public TIME = uint64(block.timestamp); // Slot 0.
mapping(address => uint256) public myMap; // Slot 1.
constructor() {
myMap[msg.sender] = TIME;
}
function getMap() public view returns (bytes32) {
assembly {
// Get free memory location.
let freeMemLoc := mload(0x40)
// Store our key, `msg.sender` at the free memory.
mstore(freeMemLoc, caller())
// Set the next free memory to be the start of the next 32-byte slot location,
// as we've stored the `msg.sender` in the current freeMemLoc location.
let nextFreeMemLoc := add(freeMemLoc, 0x20)
// Store the slot of the mapping (1) in the next free memory location.
mstore(nextFreeMemLoc, 0x01)
// Hash from start to end.
let keyLocation := keccak256(freeMemLoc, 0x40)
// Get the value of the key in the mapping.
let value := sload(keyLocation)
// Store value in memory and return.
mstore(0x80, value)
return(0x80, 0x20)
}
}
}
Nested mapping
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Yul {
mapping(address => mapping(uint256 => uint256)) public myNestedMap;
function getMap(uint256 index) public view returns (uint256) {
return myNestedMap[msg.sender][index];
}
}
A nested mapping
can have two or more keys.
To load data from a nested mapping, the number of hashes to be done must be equal to the number of keys in the map. As we saw earlier, our previous single mapping had one key, and we did only one hash. A mapping with two keys will require two hashes to get to the part of storage holding its value. The first hash, would be exactly as the one above where we hash a memory concatenation of the first key and the slot. The corresponding hashes would be a concatenation of the next key and the hash value of the previous hash.
Let us see what we mean. We will try to store a number on index 5
of the msg.sender
and we will retrieve it using
Yul.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Yul {
uint8 public INDEX = 5; // Slot 0.
uint64 public TIME = uint64(block.timestamp); // Slot 0.
mapping(address => mapping(uint256 => uint256)) public myNestedMap; // Slot 1.
constructor() {
myNestedMap[msg.sender][INDEX] = TIME;
}
function getNestedMap() public view returns (bytes32) {
assembly {
// Get free memory location.
let freeMemLoc := mload(0x40)
// Store our first key, `msg.sender` at the free memory.
mstore(freeMemLoc, caller())
// Set the next free memory to be the start of the next 32-byte slot location,
// as we've stored the `msg.sender` in the current freeMemLoc location.
let nextFreeMemLoc := add(freeMemLoc, 0x20)
// Store the slot of the mapping (1) in the next free memory location.
mstore(nextFreeMemLoc, 0x01)
// Hash from start to end.
let innerHash := keccak256(freeMemLoc, 0x40)
// This is the first hash retrieved. To get the actual location, there
// will be a second hash.
// We simply repeat the process as above, only concatenating the next
// key and the previous hash value.
// INDEX == 5, `0x05` in hexadecimal.
mstore(freeMemLoc, 0x05)
mstore(nextFreeMemLoc, innerHash)
let location := keccak256(freeMemLoc, 0x40)
// Get the value of the key in the mapping.
let value := sload(location)
// Store value in memory and return.
mstore(0x80, value)
return(0x80, 0x20)
}
}
}
Depending on how many keys are in your nested mapping, you simply have to repeat the entire process.