.slot - returns the slot location (in uint256) that the variable is located in. Determined at compile-time and doesn’t change
sstore has no consideration for existing variables already declared or stored in the contract, so using it may override values that are already there
contract YulStorage {uint x =2; // slot 0 uint y =5; // slot 1 uint z =10; // slot 2 functiongetXYul() externalviewreturns (uint256 ret) {assembly { ret :=sload(x.slot) } }functiongetVarYul(uint256 slot) externalviewreturns (bytes32 ret) {assembly { ret :=sload(slot) } }// risky, shouldn't be used unless you know what you're doing functionsetVarYul(uint256 slot,uint256 value) external {assembly {sstore(slot, value) } }}
Storage Offsets & Bitshifting
Multiple variables can be stored within a slot
contract StorageBits {uint128public C =4;uint96public D =6;uint16public E =8;uint8public F =1;functionreadBySlot(uint256 slot) externalviewreturns (bytes32 value) {assembly { value :=sload(slot) } }}
Calling readBySlot returns the below bytes32 data, where all variables C to F are stored in slot 0:
0x0001// F 0008// E 000000000000000000000006// D 00000000000000000000000000000004// C
To derive the specific values with Yul, we have to use the .offset helper
.offset is the number of bytes to the left in a bytes32 object that we have to look in order to find the location of the variable we want
functiongetOffsetE() externalpurereturns (uint256 slot,uint256 offset) {assembly { slot := E.slot // returns 0 offset := E.offset // returns 28 -> shift 28 bytes to the left } }
To obtain the value of E:
shr(x, y) - shift value y right by x number of bits
functionreadE() externalviewreturns (uint256 e) {assembly {let value :=sload(E.slot) // must load in 32 byte increments// E.offset = 28let shifted :=shr(mul(E.offset,8), value)// 0x0000000000000000000000000000000000000000000000000000000000010008// equivalent to// 0x000000000000000000000000000000000000000000000000000000000000ffff e :=and(0xffff, shifted) } }
and is a bitwise operation that compares values bit by bit
1 and 0 returns 0
0 and F returns 0
0 and 8 returns 8 → we get E = 8
In order to write to specific values in a slot (while not overriding all the values in the slot), we need to use bit masking & bit shifting
// masks can be hardcoded because variable storage slot and offsets are fixed// V and 00 = 00// V and FF = V// V or 00 = V// function arguments are always 32 bytes long under the hoodfunctionwriteToE(uint16 newE) external {assembly {// newE = 0x000000000000000000000000000000000000000000000000000000000000000a - decimal 10 let c :=sload(E.slot) // slot 0// c = 0x0001000800000000000000000000000600000000000000000000000000000004let clearedE :=and( c,0xffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff )// mask = 0xffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff// c = 0x0001000800000000000000000000000600000000000000000000000000000004// clearedE = 0x0001000000000000000000000000000600000000000000000000000000000004let shiftedNewE :=shl(mul(E.offset,8), newE)// shiftedNewE = 0x0000000a00000000000000000000000000000000000000000000000000000000let newVal :=or(shiftedNewE, clearedE)// shiftedNewE = 0x0000000a00000000000000000000000000000000000000000000000000000000// clearedE = 0x0001000000000000000000000000000600000000000000000000000000000004// newVal = 0x0001000a00000000000000000000000600000000000000000000000000000004sstore(C.slot, newVal) } }
first load the slot that contains E
selectively delete the bytes that represent E by using bitwise and to conduct masking
shift the new E value by 28 bytes and conduct bitwise or to combine the E value and current slot values together
call sstore to set the new value into the storage slot
Storage of Arrays & Mappings
For fixed arrays, essentially the same as declaring 3 uint256 variables that will take up slots 0, 1 and 2.
For dynamic arrays, the data that’s being stored in the storage slot is the length of the array.
Items in a dynamic array will not be stored sequentially down the slots, like in a fixed array, because the array could overrun and clash with other variables in the following slots
For mappings, Solidity takes the hash of the mapping’s storage slot, and concatenates it with the mapping key, then stores the value in the corresponding slot location