Expressions
Static offsets work for simple variables, but most interesting data has locations that depend on runtime values. Expressions let pointers compute addresses dynamically.
Why expressions are needed
Consider reading element i from a memory array. The element's location
depends on:
- Where the array starts (might come from the free memory pointer)
- Which element we want (the index
i) - How big each element is (32 bytes for
uint256)
A static pointer can't capture this. Expressions can:
{
"group": [
{
"name": "array-start",
"location": "memory",
"offset": 0,
"length": 32
},
{
"define": {
"index": 2
},
"in": {
"name": "element",
"location": "memory",
"offset": {
"$sum": [
{
"$read": "array-start"
},
{
"$product": [
"index",
32
]
}
]
},
"length": 32
}
}
]
}
Arithmetic expressions
Basic math operations for computing addresses:
$sum — Addition
Adds all values in an array:
{
"location": "storage",
"slot": {
"$sum": [
100,
32,
4
]
}
}
$difference — Subtraction
Subtracts the second value from the first (saturates at zero):
{
"location": "storage",
"slot": {
"$difference": [
100,
32
]
}
}
$product — Multiplication
Multiplies all values in an array:
{
"location": "memory",
"offset": {
"$product": [
32,
10
]
},
"length": 32
}
$quotient — Division
Integer division of first value by second:
{
"location": "storage",
"slot": {
"$quotient": [
100,
32
]
}
}
$remainder — Modulo
Remainder after division:
{
"location": "storage",
"slot": {
"$remainder": [
100,
32
]
}
}
Reading values
$read — Read from a named region
Reads the bytes from a previously defined region:
{
"group": [
{
"name": "array-length-slot",
"location": "storage",
"slot": 5
},
{
"name": "array-data",
"location": "storage",
"slot": {
"$keccak256": [
{
"$wordsized": 5
}
]
},
"length": {
"$product": [
{
"$read": "array-length-slot"
},
32
]
}
}
]
}
The $read expression retrieves the actual runtime value stored in the
array-length-slot region—the array's length.
Region property lookups
Reference properties of named regions with .property syntax:
.offset — Region's offset
{ ".offset": "previous-element" }
Returns the offset of the named region.
.length — Region's length
{ ".length": "previous-element" }
Returns the length of the named region.
.slot — Region's slot
{ ".slot": "base-slot" }
Returns the slot number for storage/stack/transient regions.
Chaining lookups
Compute the next element's position from the previous one:
{
"group": [
{
"name": "element-0",
"location": "memory",
"offset": "0x80",
"length": 32
},
{
"name": "element-1",
"location": "memory",
"offset": {
"$sum": [
{
".offset": "element-0"
},
{
".length": "element-0"
}
]
},
"length": 32
}
]
}
Computing storage slots with $keccak256
Solidity uses keccak256 hashing to compute storage locations for dynamic data.
Array element slots
For a dynamic array at slot n, elements start at keccak256(n):
{
"define": {
"element-index": 2
},
"in": {
"name": "element",
"location": "storage",
"slot": {
"$sum": [
{
"$keccak256": [
{
"$wordsized": 5
}
]
},
"element-index"
]
}
}
}
Mapping value slots
For a mapping at slot n, the value for key k is at keccak256(k, n):
{
"group": [
{
"name": "key-region",
"location": "stack",
"slot": 0
},
{
"name": "value",
"location": "storage",
"slot": {
"$keccak256": [
{
"$read": "key-region"
},
{
"$wordsized": 3
}
]
}
}
]
}
Nested mappings
For mapping(address => mapping(uint => uint)) at slot 2:
{
"group": [
{
"name": "outer-key",
"location": "stack",
"slot": 0
},
{
"name": "inner-key",
"location": "stack",
"slot": 1
},
{
"name": "value",
"location": "storage",
"slot": {
"$keccak256": [
{
"$read": "inner-key"
},
{
"$keccak256": [
{
"$read": "outer-key"
},
{
"$wordsized": 2
}
]
}
]
}
}
]
}
This computes: keccak256(inner_key, keccak256(outer_key, 2))
Data manipulation
$concat — Concatenate bytes
Joins byte sequences without padding:
{
"location": "storage",
"slot": {
"$keccak256": [
{
"$concat": [
"0xdead",
"0xbeef"
]
}
]
}
}
Useful for building hash inputs from multiple values.
$sized<N> — Resize to N bytes
Truncates or pads to exactly N bytes:
{
"location": "storage",
"slot": {
"$keccak256": [
{
"$sized32": "0x1234"
}
]
}
}
Pads with zeros on the left; truncates from the left if too long.
$wordsized — Resize to word size
Equivalent to $sized32 on the EVM:
{
"group": [
{
"name": "some-region",
"location": "stack",
"slot": 0
},
{
"name": "result",
"location": "storage",
"slot": {
"$keccak256": [
{
"$wordsized": {
"$read": "some-region"
}
}
]
}
}
]
}
Variables in expressions
Expressions can reference variables by name. These come from list pointer contexts:
{
"group": [
{
"name": "array-length",
"location": "storage",
"slot": 0
},
{
"list": {
"count": {
"$read": "array-length"
},
"each": "i",
"is": {
"name": "element",
"location": "storage",
"slot": {
"$sum": [
{
"$keccak256": [
{
"$wordsized": 0
}
]
},
"i"
]
}
}
}
}
]
}
The variable "i" takes values from 0 to count-1, computing each element's
slot.
Complete example: Dynamic array element
Reading element i from uint256[] storage arr at slot 5:
{
"group": [
{
"name": "array-slot",
"location": "storage",
"slot": 5
},
{
"define": {
"element-index": 2
},
"in": {
"name": "element",
"location": "storage",
"slot": {
"$sum": [
{
"$keccak256": [
{
"$wordsized": {
".slot": "array-slot"
}
}
]
},
"element-index"
]
}
}
}
]
}
The pointer:
- Defines the array's base slot
- Computes the element's slot:
keccak256(5) + element_index - Returns that storage location
Learn more
- Regions documentation for region structure
- Expression specification for the complete expression language
- Implementation guide for building an expression evaluator