Skip to main content

Collections

While regions describe single contiguous byte ranges, collections aggregate multiple pointers together. Collections handle cases where data structures span multiple locations or have dynamic configurations.

Why collections?

Consider a Solidity struct with multiple fields, or a dynamic array whose length isn't known at compile time. These require more than pointing to a single memory location—they need to describe relationships between multiple pointers or generate pointers based on runtime values.

Collections provide six patterns for composing pointers:

CollectionPurpose
groupCombine multiple pointers into one
listGenerate pointers for indexed sequences
conditionalChoose between pointers based on a condition
scopeDefine variables for use in nested pointers
referenceInvoke reusable pointer templates
templatesDefine inline templates for local reuse

Click "▶ Try it" on any example to load it into the Pointer Playground drawer at the bottom of the screen.

Group

A group combines multiple pointers into a single composite pointer. Each pointer in the group can have a name for identification.

Struct with two fields
{
"group": [
{
"name": "len",
"location": "storage",
"slot": 0
},
{
"name": "data",
"location": "storage",
"slot": 1
}
]
}

Groups are useful for structs and other compound data types where multiple fields need to be accessed together.

List

A list generates a sequence of pointers based on a count expression. This handles dynamic arrays and other indexed collections.

Dynamic array (3 elements)
{
"list": {
"count": 3,
"each": "i",
"is": {
"name": "element",
"location": "storage",
"slot": {
"$sum": [
10,
"i"
]
}
}
}
}

The list evaluates count to determine how many pointers to generate, then for each index (bound to the variable named by each), it evaluates the is pointer template.

List with dynamic count

When the count comes from storage:

Array with length from storage
{
"group": [
{
"name": "arrayLength",
"location": "storage",
"slot": 0
},
{
"list": {
"count": {
"$read": "arrayLength"
},
"each": "i",
"is": {
"name": "element",
"location": "storage",
"slot": {
"$sum": [
1,
"i"
]
}
}
}
}
]
}

List properties

PropertyDescription
countExpression evaluating to the number of items
eachVariable name for the current index (starting at 0)
isPointer template evaluated for each index

Conditional

A conditional selects between pointers based on whether an expression evaluates to a non-zero value.

Conditional pointer
Click 'Try it' and change slot 0 to 0x00 to see the 'else' branch
{
"group": [
{
"name": "flag",
"location": "storage",
"slot": 0
},
{
"if": {
"$read": "flag"
},
"then": {
"name": "whenTrue",
"location": "storage",
"slot": 1
},
"else": {
"name": "whenFalse",
"location": "storage",
"slot": 2
}
}
]
}

Conditional properties

PropertyDescription
ifExpression to evaluate (non-zero = true)
thenPointer to use when condition is true
elseOptional pointer when condition is false

Scope

A scope defines variables that can be used in a nested pointer. Variables are evaluated in order, so later variables can reference earlier ones.

Scope with computed slot
Computes slot 3 + 2 = 5
{
"define": {
"baseSlot": 3,
"offset": 2
},
"in": {
"name": "result",
"location": "storage",
"slot": {
"$sum": [
"baseSlot",
"offset"
]
}
}
}

Scopes help break complex pointer definitions into readable steps. For examples combining scopes with keccak256 for storage slot computation, see the expressions documentation.

Scope properties

PropertyDescription
defineObject mapping variable names to expressions
inPointer where defined variables are available

Reference and Templates

A reference invokes a named pointer template, while templates defines them inline. These work together for reusable pointer patterns.

Template with reference
Defines a template and uses it twice
{
"templates": {
"read-slot": {
"expect": [
"n"
],
"for": {
"name": "slot",
"location": "storage",
"slot": "n"
}
}
},
"in": {
"group": [
{
"define": {
"n": 0
},
"in": {
"template": "read-slot"
}
},
{
"define": {
"n": 1
},
"in": {
"template": "read-slot"
}
}
]
}
}

Template definition

Each template in the templates object has:

PropertyDescription
expectArray of required variable names
forThe pointer template body

Reference properties

PropertyDescription
templateName of the template to invoke
yieldsOptional object mapping template region names to new names

Nesting collections

Collections can be nested to build complex pointer structures. A group might contain lists, conditionals might wrap groups, and scopes can define variables used throughout nested collections.

Nested: scope + conditional + list
Reads length, checks if non-zero, then generates that many pointers
{
"group": [
{
"name": "lengthSlot",
"location": "storage",
"slot": 0
},
{
"define": {
"len": {
"$read": "lengthSlot"
}
},
"in": {
"if": "len",
"then": {
"list": {
"count": "len",
"each": "i",
"is": {
"name": "item",
"location": "storage",
"slot": {
"$sum": [
1,
"i"
]
}
}
}
}
}
}
]
}

Named regions in collections

Pointers within collections can include a name property. Named regions are tracked during resolution and can be referenced using the .slot, .offset, and .length syntax in expressions.

Region reference with .slot
Second region's slot is computed from first region's slot (0 + 1 = 1)
{
"group": [
{
"name": "header",
"location": "storage",
"slot": 0
},
{
"name": "body",
"location": "storage",
"slot": {
"$sum": [
{
".slot": "header"
},
1
]
}
}
]
}

Learn more