Pointer playground
A pointer is a recipe for finding data in the EVM: where bytes live, and how to compute that location from the current machine state. The examples below resolve real Solidity storage layouts against concrete state — click "Try it" to open the playground and watch each pointer select its bytes.
They build up from a single packed variable to the full short/long string layout, where the format really earns its keep.
A packed storage variable
Solidity packs small values so several share a slot. This pointer reads a
20-byte address stored at byte 12 of slot 0 — a sub-slot region, offset
and length inside a single word.
{
"location": "storage",
"slot": 0,
"offset": 12,
"length": 20
}
A dynamic array element
A uint256[] at slot 5 stores its elements starting at keccak256(5), so
element i lives at keccak256(5) + i. This pointer computes the slot for
element 2.
{
"define": {
"element-index": 2
},
"in": {
"name": "element",
"location": "storage",
"slot": {
"$sum": [
{
"$keccak256": [
{
"$wordsized": 5
}
]
},
"element-index"
]
}
}
}
A mapping value
A mapping(address => uint256) at slot 3 stores each value at
keccak256(key, 3). This pointer reads the key from the stack, hashes it
with the slot, and resolves the value's location.
{
"group": [
{
"name": "key-region",
"location": "stack",
"slot": 0
},
{
"name": "value",
"location": "storage",
"slot": {
"$keccak256": [
{
"$read": "key-region"
},
{
"$wordsized": 3
}
]
}
}
]
}
A Solidity storage string
Strings are the real test. Solidity packs a short string (31 bytes or
fewer) directly into its slot, with 2 × length in the slot's last byte. A
long string instead stores 2 × length + 1 in the slot and puts the
data at keccak256(slot), spanning as many following slots as it needs.
One pointer handles both: it reads the last byte, and the parity of that flag selects the short branch or the long branch. It's the same pointer in both examples below — only the state changes.
Short string
Slot 0 packs the bytes of "Hello" with 0x0a (that's 2 × 5) in its
last byte. The flag is even, so the pointer reads the string straight out
of the slot.
{
"define": {
"slot": 0
},
"in": {
"group": [
{
"name": "length-flag",
"location": "storage",
"slot": "slot",
"offset": 31,
"length": 1
},
{
"if": {
"$remainder": [
{
"$sum": [
{
"$read": "length-flag"
},
1
]
},
2
]
},
"then": {
"define": {
"string-length": {
"$quotient": [
{
"$read": "length-flag"
},
2
]
}
},
"in": {
"name": "string",
"location": "storage",
"slot": "slot",
"offset": 0,
"length": "string-length"
}
},
"else": {
"group": [
{
"name": "long-string-length-data",
"location": "storage",
"slot": "slot",
"offset": 0,
"length": 32
},
{
"define": {
"string-length": {
"$quotient": [
{
"$difference": [
{
"$read": "long-string-length-data"
},
1
]
},
2
]
},
"start-slot": {
"$keccak256": [
{
"$wordsized": "slot"
}
]
},
"total-slots": {
"$quotient": [
{
"$sum": [
"string-length",
{
"$difference": [
32,
1
]
}
]
},
32
]
}
},
"in": {
"list": {
"count": "total-slots",
"each": "i",
"is": {
"define": {
"current-slot": {
"$sum": [
"start-slot",
"i"
]
},
"previous-length": {
"$product": [
"i",
32
]
}
},
"in": {
"if": {
"$difference": [
"string-length",
{
"$sum": [
"previous-length",
32
]
}
]
},
"then": {
"name": "string",
"location": "storage",
"slot": "current-slot"
},
"else": {
"name": "string",
"location": "storage",
"slot": "current-slot",
"offset": 0,
"length": {
"$difference": [
"string-length",
"previous-length"
]
}
}
}
}
}
}
}
]
}
}
]
}
}
Long string
Here the flag is 0x51 (2 × 40 + 1), so the pointer takes the long
branch: it computes the length as (0x51 − 1) / 2 = 40, finds the data at
keccak256(0), and reads 40 bytes across that slot and the next.
{
"define": {
"slot": 0
},
"in": {
"group": [
{
"name": "length-flag",
"location": "storage",
"slot": "slot",
"offset": 31,
"length": 1
},
{
"if": {
"$remainder": [
{
"$sum": [
{
"$read": "length-flag"
},
1
]
},
2
]
},
"then": {
"define": {
"string-length": {
"$quotient": [
{
"$read": "length-flag"
},
2
]
}
},
"in": {
"name": "string",
"location": "storage",
"slot": "slot",
"offset": 0,
"length": "string-length"
}
},
"else": {
"group": [
{
"name": "long-string-length-data",
"location": "storage",
"slot": "slot",
"offset": 0,
"length": 32
},
{
"define": {
"string-length": {
"$quotient": [
{
"$difference": [
{
"$read": "long-string-length-data"
},
1
]
},
2
]
},
"start-slot": {
"$keccak256": [
{
"$wordsized": "slot"
}
]
},
"total-slots": {
"$quotient": [
{
"$sum": [
"string-length",
{
"$difference": [
32,
1
]
}
]
},
32
]
}
},
"in": {
"list": {
"count": "total-slots",
"each": "i",
"is": {
"define": {
"current-slot": {
"$sum": [
"start-slot",
"i"
]
},
"previous-length": {
"$product": [
"i",
32
]
}
},
"in": {
"if": {
"$difference": [
"string-length",
{
"$sum": [
"previous-length",
32
]
}
]
},
"then": {
"name": "string",
"location": "storage",
"slot": "current-slot"
},
"else": {
"name": "string",
"location": "storage",
"slot": "current-slot",
"offset": 0,
"length": {
"$difference": [
"string-length",
"previous-length"
]
}
}
}
}
}
}
}
]
}
}
]
}
}
Learn more
- Regions for locations and addressing schemes
- Collections for groups, lists, and conditionals
- Expressions for arithmetic,
reads, and
$keccak256