Skip to main content

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:

Array element access
Computes offset: array-start value (0x40) + index (2) × 32 = 0x80
{
"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:

$sum expression
Computes slot: 100 + 32 + 4 = 136 (0x88)
{
"location": "storage",
"slot": {
"$sum": [
100,
32,
4
]
}
}

$difference — Subtraction

Subtracts the second value from the first (saturates at zero):

$difference expression
Computes slot: 100 - 32 = 68 (0x44)
{
"location": "storage",
"slot": {
"$difference": [
100,
32
]
}
}

$product — Multiplication

Multiplies all values in an array:

$product expression
Computes offset: 32 × 10 = 320 (0x140)
{
"location": "memory",
"offset": {
"$product": [
32,
10
]
},
"length": 32
}

$quotient — Division

Integer division of first value by second:

$quotient expression
Computes slot: 100 ÷ 32 = 3
{
"location": "storage",
"slot": {
"$quotient": [
100,
32
]
}
}

$remainder — Modulo

Remainder after division:

$remainder expression
Computes slot: 100 mod 32 = 4
{
"location": "storage",
"slot": {
"$remainder": [
100,
32
]
}
}

Reading values

$read — Read from a named region

Reads the bytes from a previously defined region:

$read expression
Reads array length (3) from slot 5, computes data length as 3 × 32 = 96 bytes
{
"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:

Chaining .offset and .length
element-1 offset = 0x80 + 32 = 0xa0
{
"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):

Array element slot via keccak256
Computes: keccak256(5) + 2
{
"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):

Mapping slot via keccak256
Computes: keccak256(key, 3) — standard Solidity mapping slot
{
"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:

Nested mapping slot
Computes: keccak256(inner_key, keccak256(outer_key, 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:

$concat expression
Concatenates 0xdead + 0xbeef = 0xdeadbeef, then hashes
{
"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:

$sized32 expression
Pads 0x1234 to 32 bytes before hashing
{
"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:

$wordsized expression
Reads value from stack, pads to word size, then hashes
{
"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:

List variable in expression
Variable 'i' takes values 0, 1, 2 computing each element's slot
{
"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:

Dynamic array element
Computes keccak256(5) + 2 to find element at index 2
{
"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:

  1. Defines the array's base slot
  2. Computes the element's slot: keccak256(5) + element_index
  3. Returns that storage location

Learn more