Have been working on a function allow users to edit specific types of state using Solidity assembly, and ran into some unexpected behavior.
According to the documentation, for a storage array arr
located at position p
, the value at p
(i.e. p + 0
) is the length of the array (i.e. does not store data):
For dynamic arrays, this slot stores the number of elements in the array.
And yet, when I attempt to mutate the value stored at position 0
(p + 0
) using sstore
, in fact it is the first element of the array arr[0]
which is affected. I would have expected to need to add an additional offset (i.e. p + 1
) to reach location arr[0]
.
Did I misunderstand the documentation? Does Solidity assembly know enough about the underlying storage structure to prevent me from mutating the array length directly?
Here is the function used to determine the location to be mutated. The idea is that you begin with a base storage location of a mapping, and then process a sequence of mapping keys or offsets to get the final storage location for your variable.
bool constant MAPPING = false;
bool constant OFFSET = true;
function changeState(
bytes32 baseSlot,
bool[] memory mask,
bytes32[] memory keys,
bytes32 value
)
public
{
require(mask.length == keys.length, "bad-num-keys");
bytes32 slot = baseSlot;
for (uint256 i; i < keys.length; i++) {
if (mask[i] == MAPPING) {
slot = keccak256(abi.encode(keys[i], slot));
}
if (mask[i] == OFFSET) {
slot = bytes32(add(uint256(keys[i]), uint256(slot)));
if (i != keys.length - 1) { // If not last offset
slot = keccak256(abi.encode(slot));
}
}
assembly {
sstore(slot, value)
}
}
And here is the storage structure I am working with:
struct MyStruct {
address addr;
uint256 val1;
uint256 val2;
uint256[] otherVals;
}
mapping(uint256 => MyStruct) myStructs;
Using this code, I was expecting to pass the following to edit the first value of the otherVals
storage array, located in the myStructs
mapping with a key of 0
:
const baseSlot = myStructs_slot;
const mask = [F, T, T];
const keys = [0, 3, 1];
But found that the following worked:
const baseSlot = myStructs_slot;
const mask = [F, T, T];
const keys = [0, 3, 0];
sstore(p, value)
p
is defined.