Skip to main content

Test case: <struct> storage

Solidity tightly packs struct storage words starting from the right-hand side. This test ensures that relative offsets are computed properly for a struct with a few small fields (struct Record { uint8 x; uint8 y; bytes4 salt; }).

Test source

const structStorageTest: ObserveTraceTest<{
x: number;
y: number;
salt: string;
}> = {
pointer: findExamplePointer("struct-storage-contract-variable-slot"),
compileOptions: singleSourceCompilation({
path: "StructStorage.sol",
contractName: "StructStorage",
content: `contract StructStorage {
Record record;

uint8 step;

constructor() {
record = Record({
x: 5,
y: 8,
salt: 0xdeadbeef
});

// trick the optimizer maybe (otherwise the first record assignment
// will get optimized out)
//
// compiler might be smarter in the future and cause this test to fail
step = 1;

record = Record({
x: 1,
y: 2,
salt: 0xfeedface
});

step = 2;
}
}

struct Record {
uint8 x;
uint8 y;
bytes4 salt;
}
`
}),

expectedValues: [
{ x: 0, y: 0, salt: "0x" },
{ x: 5, y: 8, salt: "0xdeadbeef" },
{ x: 1, y: 2, salt: "0xfeedface" },
],

async observe({ regions, read }) {
const x = Number(
(await read(regions.lookup["x"])).asUint()
);

const y = Number(
(await read(regions.lookup["y"])).asUint()
);

const salt = (await read(regions.lookup["salt"])).toHex();

return { x, y, salt };
},

equals(a, b) {
return a.x === b.x && a.y === b.y && a.salt === b.salt;
}
};

Tested pointer

# example `struct Record { uint8 x; uint8 y; bytes4 salt; }` in storage
define:
"struct-storage-contract-variable-slot": 0
in:
group:
- name: "x"
location: storage
slot: "struct-storage-contract-variable-slot"
offset:
$difference:
- $wordsize
- .length: $this
length: 1 # uint8
- name: "y"
location: storage
slot: "struct-storage-contract-variable-slot"
offset:
$difference:
- .offset: "x"
- .length: $this
length: 1 # uint8
- name: "salt"
location: storage
slot: "struct-storage-contract-variable-slot"
offset:
$difference:
- .offset: "y"
- .length: $this
length: 4 # bytes4