Another week, another report of my progress in the Expert Solidity Bootcamp powered by Encode! Week 2 has ended and here I am going to explain how it went, as well as report some of the rabbit holes I’ll go through and proof of concepts I’ll like to develop.
Week 2 – Summary
Week 2 has been slightly different from the first week, not so much about the topics but the way they were approached. In this case, we’ve had way more hands on tasks and group work, instead of reviewing theoretical material (we did it but way less).
There new concepts which required me to invest more time in getting up to speed. Specifically working with assembly, opcodes and the Yul programming language. Here are a couple of code practices I did along the way:
Assembly
Assembly is used for optimizing our code, as the compiler is (still) not very good at it. It is used Reverse Polish Notation: i.e. 2+4 would be:
PUSH1 2
PUSH1 4
ADD
Another example, adding 3 to the contents in memory at position 0x80 would be:
mstore(0x80, add(mload(0x80), 3))3 0x80 mload add 0x80 mstore
Free Memory Pointer
- The free memory pointer points to
0x80initially. - To set up the free memory pointer is used:
PUSH1 0x80
PUSH1 0x40
MSTORE
Yul
- Variable declarations: stored on the stack (do not directly influence memory or storage, but can be used as pointers to memory or storage)
- Example:
let y:= 4 - creates a new stack slot
- the new slot is reserved for the variable
- the slot is automatically removed again when the end of the block is reached
- Example:
- Accessing variables: can be accessed from outside the block if they are included in the same function
- slot and byte-offset: for local storage variables or state variables, a single identifier is not sufficient as they do not always occupy a single full slot.
x.soltto retrieve the slot pointed to by the variablexx.offsetto retrieve the byte-offset
- You can assign to the
.slotpart of a local storage variable pointer. For these (strucs, arrays or mappings) the.offsetpart is always zero - It is not possible to assign teh
.soltor.offsetpart of a state variable
- slot and byte-offset: for local storage variables or state variables, a single identifier is not sufficient as they do not always occupy a single full slot.
function example() ... {
uint a = 5;
assembly {
let x:= add(2, a)
}
}
- Literals: same as in Solidity
- Example:
let a := 0x123or `let b := "hello"
- Example:
- If statements: there is no else
- Example: if a is smaller than b
assembly {
if lt(a, b) { sstore(0, 1) }
}
- Switch statements: it does not flow from one case to the next and default case is not allowed if all possible values of the expression type are covered
- Loops: there isn’t a while loop, only for.
- Memory:
- 0x00 -0x3f (64 bytes): scratch space for hashing methods
- 0x40 – 0x5f (32 bytes): free memory pointer
- 0x60 – 0x7f (32 bytes): zero slot, should never be written to (used as initial value for dynamic memory arrays)
- Functions: there is no idea of public/internal/private visibility. Assembly functions are not part of the external interface of the contract, only visible in the block that they are defined.
- Addresses and Selectors: you can return
fun.selectorandfun.address - Memoryguard keyword:
let ptr := memoryguard(size)promises to only use the memory in the range [0,size] - Verbatim keword: create bytecode for opcodes that are not known to the Yul compiler. Also create bytecode sequences that will not be modified by the optimizer.
Some OpCodes
CODECOPY: copies bytes from this contract into memoryEXTCODECOPY: copies bytes from another contract into memoryCREATE: create a contract. The created address is foundhash(sender, nonce)CREATE2: create a contract with a deterministic address, providing the creation bytecode (init code).hash(0xFF, sender, salt, bytecode)CREATE3: create a contract with a deterministic address, without including the initcode.hash(salt, creationCode)
Some Tools
Wormholes
- Reverse Polish Notation
- Init code, Creation Bytecode, Runtime Bytecode