I'd do something like this, using call() to do an external function call, passing the msg.value inside it.
function () payable {
bytes4 sig = bytes4(keccak256("()")); // function signature
assembly {
let x := mload(0x40) // get empty storage location
mstore ( x, sig ) // 4 bytes - place signature in empty storage
let ret := call (gas,
beneficiary,
msg.value,
x, // input
0x04, // input size = 4 bytes
x, // output stored at input location, save space
0x0 // output size = 0 bytes
)
mstore(0x40, add(x,0x20)) // update free memory pointer
}
}
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 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 :
- slot 0 : [amount (0x10) - level (0x0)]
- slot 1 : [time (0x10) - num (0x0)]
- slot 2 : [sender (0x0)]
Now keep in mind that uint128 take 16 bytes of memory, and address takes 20.
With :
a := and(w, 0xfff) // gets first value in first slot
You are only getting the first 12 bits (1.5 byte) of the level
member but level
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 :
function getValues(uint _id) public view returns(uint a, uint b, uint c, uint d, address e){
Info storage info = infos[_id];
assembly {
// Load info.slot
let w := sload(info.slot)
// Get the first 16 bytes of sload(info.slot) : level
a := and(w, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
// Shift sload(info.slot) 16 bytes right : amount
// Values in assembly are unsigned int by default, shifting right pads with 0s
// So, no need to apply the mask here but you could if you wanted to
b := shr(128, w)
// Load info.slot + 1
w:= sload(add(info.slot, 1))
// Get the first 16 bytes of sload(info.slot + 1) : num
c := and(w, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
// Shift sload(info.slot + 1) 16 bytes right : time
d := shr(128, w)
// Load info.slot + 2 and take the first 20 bytes : sender
e := and(sload(add(info.slot, 2)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
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).
We'll just use the same ideas for that one by being carefull not to change anything else than the level
field like so :
function writeTo(uint _id) public {
Info storage info = infos[_id];
uint newLevel = 10;
assembly {
// Load info.slot : [amount, level]
let w := sload(info.slot)
// Clear the first 16 bytes (level)
w := and(w, not(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
// Write the first 16 bytes with the first 16 bytes of newLevel
w := or(w, and(newLevel, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
// Update the storage value
sstore(info.slot, w)
}
}
If some bitwise operations are a bit confusing, I always end up on this great answer describing them.
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)
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)
where h(k)
is just the key in your case that is _id
and p
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 :
function getStorageSlotAssembly(uint _id) public view returns (bytes32 slot) {
assembly {
mstore(0x00, _id)
mstore(0x20, infos.slot)
slot := keccak256(0, 0x40)
}
}
function getStorageSlotSolidity(uint _id) public view returns (bytes32 slot) {
Info storage info = infos[_id];
assembly {
slot := info.slot
}
}
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.
Best Answer
See the Access to External Variables, Functions and Libraries section in the docs.
The solution is to use the
sload
function and pass the slot as the only argument:Caveats:
address
). You cannot assume that the bits not part of the encoding will be zero..offset
and.length
besides.slot
.