Skip to main content

Test case: uint256[] memory

Memory arrays are primarily referenced using stack-located memory offset values, and so this test case ensures that stack slot indexes are properly adjusted over the course of the transaction.

Test source

const uint256ArrayMemoryTest: ObserveTraceTest<number[]> = {
pointer: findExamplePointer("uint256-array-memory-pointer-slot"),
compileOptions: singleSourceCompilation({
path: "Uint256Arraymemory.sol",
contractName: "Uint256ArrayMemory",
content: `contract Uint256ArrayMemory {
constructor() {
uint256[] memory values = new uint256[](0);
values = appendToArray(values, 1);
values = appendToArray(values, 2);
values = appendToArray(values, 3);
}

function appendToArray(
uint256[] memory arr,
uint256 value
)
private
pure
returns (uint256[] memory)
{
uint256[] memory newArray = new uint256[](arr.length + 1);

for (uint i = 0; i < arr.length; i++) {
newArray[i] = arr[i];
}

newArray[arr.length] = value;
return newArray;
}
}
`
}),

expectedValues: [
[],
[1],
[1, 2],
[1, 2, 3]
],

async observe({ regions, read }, state): Promise<number[]> {
const items = regions.named("array-item");

return (await Promise.all(
items.map(async (item) => {
const data = await read(item);

return Number(data.asUint());
})
));
},

equals(a, b) {
if (a.length !== b.length) {
return false;
}

for (const [index, value] of a.entries()) {
if (b[index] !== value) {
return false;
}
}

return true;
},

// this function uses observation of solc + viaIR behavior to determine
// that the memory array we're looking for is going to have a pointer at
// the bottom of the stack
//
// also include a check to exclude observation when that bottom stack value
// would have `cursor.view()` yield more regions than expected
async shouldObserve(state) {
const stackLength = await state.stack.length;
if (stackLength === 0n) {
return false;
}

// only consider the bottom of the stack
const arrayOffset = await state.stack.peek({ depth: stackLength - 1n });

const arrayCount = await state.memory.read({
slice: {
offset: arrayOffset.asUint(),
length: 32n
}
})

// the example code only appends three times
return arrayCount.asUint() < 4n;
}
};

Tested pointer

# example `uint256[] memory` allocation pointer
define:
"uint256-array-memory-pointer-slot": 0
in:
# this pointer composes an ordered list of other pointers
group:
# declare the first sub-pointer to be the "array-start" region of data
# corresponding to the first item in the stack (at time of observation)
- name: "array-start"
location: stack
slot: "uint256-array-memory-pointer-slot"

# declare the "array-count" region to be at the offset indicated by
# the value at "array-start"
- name: "array-count"
location: memory
offset:
$read: "array-start"
length: $wordsize

# thirdly, declare a sub-pointer that is a dynamic list whose size is
# indicated by the value at "array-count", where each "item-index"
# corresponds to a discrete "array-item" region
- list:
count:
$read: "array-count"
each: "item-index"
is:
name: "array-item"
location: "memory"
offset:
# array items are positioned so that the item with index 0
# immediately follows "array-count", and each subsequent item
# immediately follows the previous.
$sum:
- .offset: "array-count"
- .length: "array-count"
- $product:
- "item-index"
- .length: $this
length: $wordsize