Inline Assembly & Yul
Inline assembly gives direct access to the EVM using Yul, a low-level language with variables, functions, if, switch, and for, but no types other than 256-bit words.
28.1 Basics
Inline assembly gives direct access to the EVM using Yul, a low-level language with variables, functions, if, switch, and for, but no types other than 256-bit words.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AsmBasics {
function add(uint256 a, uint256 b) external pure returns (uint256 c) {
assembly {
c := add(a, b) // no overflow check in assembly!
}
}
function max(uint256 a, uint256 b) external pure returns (uint256 r) {
assembly {
r := a
if gt(b, a) { r := b }
}
}
function classify(uint256 x) external pure returns (uint256 r) {
assembly {
switch x
case 0 { r := 100 }
case 1 { r := 200 }
default { r := 300 }
}
}
function sumTo(uint256 n) external pure returns (uint256 s) {
assembly {
for { let i := 1 } lt(i, add(n, 1)) { i := add(i, 1) } {
s := add(s, i)
}
}
}
function codeSize(address a) external view returns (uint256 size) {
assembly { size := extcodesize(a) }
}
}28.2 Common opcodes
add sub mul div sdiv mod smod exp addmod mulmod signextend Comparison / bitwise
lt gt slt sgt eq iszero and or xor not shl shr sar byte mload mstore mstore8 msize mcopy sload sstore tload tstore Calldata / returndata
calldataload calldatasize calldatacopy returndatasize returndatacopy
caller callvalue address balance selfbalance origin gas gasprice chainid timestamp number basefee
call delegatecall staticcall create create2 return revert stop invalid Hashing / logs keccak256 log0 … log4
28.3 Memory layout conventions
scratch space for hashing free memory pointer (next free memory address) zero slot (initial value for empty dynamic memory arrays) start of allocated memory
Mark blocks assembly ("memory-safe") { } only when they respect Solidity's memory model (use scratch space or memory beyond the free pointer); this lets the via-IR optimizer move variables to memory and avoid "stack too deep".
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AsmMemory {
// Hash two words using scratch space (no allocation)
function hashPair(bytes32 a, bytes32 b) external pure returns (bytes32 h) {
assembly ("memory-safe") {
mstore(0x00, a)
mstore(0x20, b)
h := keccak256(0x00, 0x40)
}
}
// Custom error revert in assembly: error Unauthorized()
function onlyCaller(address expected) external view {
assembly {
if iszero(eq(caller(), expected)) {
mstore(0x00, 0x82b42900) // selector of Unauthorized()
revert(0x1c, 0x04) // selector is in the last 4 bytes of the word
}
}
}
// Access storage slots and offsets of Solidity variables
uint128 public lo;
uint128 public hi;
function readPacked() external view returns (uint256 slot, uint256 offset, bytes32 raw) {
assembly {
slot := hi.slot
offset := hi.offset
raw := sload(hi.slot)
}
}
}28.4 Accessing variables
Local value variables: by name.
Storage variables: x.slot, x.offset; storage pointers can be reassigned via p.slot := ....
Calldata arrays: arr.offset, arr.length.
Memory references: the variable holds the memory address (first word = length for dynamic arrays).
Function return: assign to named return variables, or return(ptr, size) to exit the whole call immediately.
28.5 Standalone Yul
Yul can be compiled directly (solc --strict-assembly) and is the intermediate language of the via-IR pipeline. Libraries like Solady use heavily optimized assembly; study them after mastering the basics.
28.6 Assembly safety checklist
No overflow checks, no bounds checks, no type checks.
Clean dirty upper bits when reading small types (and(x, 0xff...)).
Never corrupt the free memory pointer or zero slot.
Test assembly with differential fuzzing against a pure-Solidity reference implementation.