We reject abstraction
We believe in bytecode
Bytecode is only true form
Let’s code with bytes now
Last week, we talked about Stack, Memory, and Storage, the three data regions of the EVM. Today, we’re going to explore how smart contracts make decisions and change execution paths using control flow.
Control flow is the logic that determines what happens next in a program. In Solidity, you use if, else, for, while, or function calls. In bytecode, those high-level keywords are translated into jump instructions.
The EVM is a very simple machine, it executes bytecode one instruction at a time, moving forward by default. But with control flow instructions, we can tell it to jump to a different part of the code, or conditionally jump based on a value.
Opcode | Name | Description |
|
| Jump to a specific position in the code (unconditional) |
|
| Jump to a position only if a condition is met (conditional) |
|
| Marks a valid place to jump to. You must jump only to a location with this opcode |
Let’s say you want to write a smart contract that:
Returns 0x02 if the input is greater than 10
Otherwise, returns 0x01
This is what our example looks like in pseudocode:
if (input > 10) {
return 0x02;
} else {
return 0x01;
}
And here’s what the EVM bytecode can be implemented:
Offset | Bytecode | Description |
00 | 60 0A |
|
02 | 60 00 |
|
04 | 35 |
|
05 | 11 |
|
06 | 60 0E |
|
08 | 57 |
|
False branch (default: input <= 10)
09 | 60 01 |
|
0B | 60 12 |
|
0D | 56 |
|
True branch (input > 10) starts at 0x12
0E | 5B |
|
0F | 60 02 |
|
Branch merge point, true and false branches converge here
12 | 5B |
|
13 | 60 00 |
|
15 | 52 |
|
16 | 60 20 |
|
18 | 60 00 |
|
1A | F3 |
|
The EVM doesn’t allow jumping to just any byte, it has to be a JUMPDEST
. This design prevents jumping into the middle of data or invalid instruction segments. Think of JUMPDEST
as a checkpoint you can land on.
While marking safe spots for jump instructions is considered a sound and secure virtual machine design choice, the EVM has been rightfully criticized for requiring full 32-byte absolute offsets for every jump. This approach is unnecessarily heavy. A more ergonomic and lightweight alternative would be to use relative jumps based on the current execution point (known as the program counter). In most cases, such as typical if or while logic, the jump distances are small and could easily be expressed in a single byte. The inefficiency of absolute jumps is well-known, and there is ongoing discussion in the Ethereum ecosystem about adopting relative jumps in future virtual machine designs.
Input is loaded from calldata.
Compared with 10 using the GT
(greater than) opcode.
If true, we JUMPI
to a labeled part of the code where we return 0x01.
If false, the EVM continues forward and returns 0x00 and jumps to the merge point
The merge point is where both true and false branches reunite and returns the result
Both branches store the result at the stack, then on the merge point a 32-byte value padded with zeros is returned.
Modify it and check it using this EVM Disassembler to:
Return 0xAA if the input is less than 5
Return 0xBB if the input is between 5 and 20
Return 0xCC otherwise
So far, we've explored bytecode piece by piece, but what does a real smart contract look like in raw bytecode?
Next Tuesday, we’ll take a full contract and break it down section by section. Subscribe now and see you next Tuesday for more bytecode.