In this case the slot will be 0 and the offset will be 28. This means E is stored 28 bytes from the right.
Hereās how you can get the value of E using the offset, bit shifting and masking
function readE() external view returns(uint16 e) {
assembly {
let value := sload(E.slot)
// value = 0x0001000800000000000000000000000600000000000000000000000000000004
let shifted := shr(mul(E.offset, 8), value) // multiply by 8 because 1 byte = 8 bits and shr takes bits not bytes
// shifted = 0x0000000000000000000000000000000000000000000000000000000000010008
e := and(0xffff, shifted) // mask the last 4 bytes to 1 and the rest to 0
}
}
When smaller variables are packed in a single 32-bit storage slot, this is how you write a to a single variable
here we have a fixed array, dynamic array, a small sized array, mapping, nested mapping and mapping to an array. Letās look at how the variables are stored in each case.
Fixed Array
uint256[3] fixedArray
In a fixed array, the storage slots for each elements are simply array.slot + index
If you have a fixed array of length 3, itāll take 3 consecutive storage slots.
Itās similar to just declaring 3 variables back to back.
function fixedArrayView(uint256 index) external view returns (uint256 ret) {
assembly {
ret := sload(add(fixedArray.slot, index))
}
}
Dynamic Array
uint256[] dynamicArray
In a dynamic array, the length of the array is stored in dynamicArray.slot.
function dynamicArrayLength() external view returns (uint256 ret) {
assembly {
ret := sload(dynamicArray.slot)
}
}
Each index of the array is stored in keccack256(abi.encode(dynamicArray.slot)) + index
Memory only has 4 operations mstore, mload , mstore8 and msize
In pure Yul program, memory is easy to use. But it gets tricky with solidity + Yul since solidity expects memory to be used in a certain way.
Memory is cheaper compared to storage. But the further memory you try to access, the more gas it takes. This is to disincentivise contracts from using too much of the nodeās memory.
eg mload(0xffffffffffffffffffffffffff) will run out of gas
so you canāt randomly access memory from hash like we did for storage.
mstore(p, v) - stores v in memory slot p
mload(p) - retreives 32 bytes from memory slot p[p, p+0x20]
mstore8(p, v) - like mload but for 1 byte
msize - largest accessed memory index in the transaction
mstore works very similar to sstore . Itāll store the value in the 32 byte slot.
With mstore8, it only store the data in the given byte
How Solidity uses Memory
Solidity allocates slots [0x00, 0x20), [0x20, 0x40) (0 - 63) for āscratch spaceā. You can write values here and expect it to be ephermal.
Solidity reserves slot [0x40, 0x60) as the free memory pointer. If you want to write something new to memory, this slot tells you where the next free memory is
Solidity keeps [0x60, 0x80) empty
You start writing your memory variables starting from 0x80
In the below code we are first checking the value of the free memory pointer and then storing a struct to memory and checking the free memory pointer again
The first emitted event will show the free memory to be 0x80 and the next event will show it as 0xc0.
The struct holds 64 bytes of data (2 * 32 bytes(uint256)).
0xc0 - 0x80 = 64 . The free memory pointer moved by 64 bytes to account for P in memory.
abi.encode works similar to an array in terms of memory. But it stores extra 20 bytes in the starting of itās memory. This is the total size of the arguments.
The first event will emit 0x80 and the second will emit 0xe0 . 0xe0 - 0x80 = 96 bytes . This contains 2 * 32 bytes(arguments) + 1 * 32 bytes(length) .
structs and arrays. but you need to explicitly mention memory keyword
since objects are laid end-to-end itās not possible to grow arrays. that is why arrays in memory canāt do push unlike arrays in storage. becuase it might crash into the value next to it.
Yul
The variable itself is where it begins in memory
To access a dynamic array in Yul, you need to add 32 bytes or 0x20 to skip the length.
Call this function passing [5, 7] as the argument. The location will be 0x80 since itās the first variable. The length of the array will be stored in the location of the array. So mload(location) will return 2 which is the length of the array.
Each value will be stored 32 bytes from the previous value starting from the location.