I'm trying to read from and write to a mapping of structs using inline assembly.
getValues()
is an example of how I'm able to read something from storage. I can read the first two values using and()
and shr()
. But how do I read the next values within struct?
The code in writeTo()
is able to write to a mapping(uint => uint)
. But I cant figure out how to write to a slot in a struct within a mapping(uint => Struct)
.
What values should be hashed for sstore()
to write to correct location? (whatever location that might be, but I assume getting the answer to how to jump to next slot within the struct using shr()
(?) might answer that question)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;
contract Assemble2{
mapping(uint => Info) infos;
struct Info{
uint128 level;
uint128 amount;
uint128 num;
uint128 time;
address sender;
}
constructor(){
infos[0] = Info(
2,3, 4,5, msg.sender
);
}
function getValues(uint _id) public view returns(uint a, uint b, uint c, uint d, address e){
Info storage info = infos[_id];
assembly {
let w := sload(info.slot)
a := and(w, 0xfff) // gets first value in first slot
b := shr(128, w) // gets second value in first slot
//c := shr(?, w) // how to jump to next slot and get first value?
//d := shr(?, w) // how to jump to next slot and get second value?
//e := shr(?, w) // how to jump to third slot and get first value?
}
}
function writeTo(uint _id) public {
Info storage info = infos[_id];
uint newLevel = 10;
assembly {
mstore(0, newLevel)
mstore(32, info.slot)
// how to include position within struct?
// do I somehow hash the sload with the pointer to the position?
// let w := sload(info.slot)
// b := shr(128, w)
let hash := keccak256(0,64)
sstore(hash,2)
}
}
}
Best Answer
The storage layout of structs is quite simple, if the stuct data doesn't fit in one slot then it will simply use the next one(s) too.
So assuming the struct storage slot is 0, the layout will be the following :
Now keep in mind that uint128 take 16 bytes of memory, and address takes 20.
With :
You are only getting the first 12 bits (1.5 byte) of the
level
member butlevel
is a 128 bit (16 bytes) value.So accounting for both the storage layout of structs and the real size of the members you are trying to read, a better
getValues
implementation could be :We'll just use the same ideas for that one by being carefull not to change anything else than the
level
field like so :If some bitwise operations are a bit confusing, I always end up on this great answer describing them.
Since you were already getting the storage slot from
info.slot
there is no need to hash anything, the hash operation is the way to compute the target slot but in your case you don't need to since it's already available...But the storage slot of a mapping element is described here,
keccak256(h(k) . p)
whereh(k)
is just the key in your case that is_id
andp
is the storage slot of the mapping itself :infos.slot
(infoS not info, we are targeting the mapping here).Those 2 functions are therefore perfectly equivalent :
Just make sure to read the doc thoroughly as h(k) might actually do something if you were to use different a key type.
I hope that answers your question.