Skip to content

Commit

Permalink
Merge pull request #96 from ethdebug/struct-storage
Browse files Browse the repository at this point in the history
Test and document the testing of pointers to structs in storage
  • Loading branch information
gnidan authored Jul 3, 2024
2 parents 22f1e8c + 6044f9d commit ed67237
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 23 deletions.
73 changes: 72 additions & 1 deletion packages/pointers/src/test-cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,76 @@ export interface ObserveTraceTest<V> extends ObserveTraceOptions<V> {
expectedValues: V[];
}

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;
}
};

const stringStorageTest: ObserveTraceTest<string> = {
pointer: findExamplePointer("string-storage-contract-variable-slot"),

Expand Down Expand Up @@ -39,7 +109,7 @@ const stringStorageTest: ObserveTraceTest<string> = {

async observe({ regions, read }: Cursor.View): Promise<string> {
// collect all the regions corresponding to string contents
const strings = await regions.named("string");
const strings = regions.named("string");

// read each region and concatenate all the bytes
const stringData: Data = Data.zero()
Expand Down Expand Up @@ -154,6 +224,7 @@ const uint256ArrayMemoryTest: ObserveTraceTest<number[]> = {
* for additional unexpected values in between and around the expected values.
*/
export const observeTraceTests = {
"struct storage": structStorageTest,
"string storage": stringStorageTest,
"uint256[] memory": uint256ArrayMemoryTest,
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ This variable will be used to generate automated tests dynamically, as will
be described on the
[next page](/docs/implementation-guides/pointers/testing/jest).

## Structs in 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 that
defines a few small fields (`struct Record { uint8 x; uint8 y; bytes4 salt; }`).

### Test source

<CodeListing
packageName="@ethdebug/pointers"
sourcePath="src/test-cases.ts"
extract={sourceFile => sourceFile.getVariableStatement("structStorageTest")}
/>

### Tested pointer

<TestedPointer
pointerQuery="struct-storage-contract-variable-slot"
/>

## Storage strings

Representing a Solidity `string storage` using an **ethdebug/format/pointer**
Expand Down
51 changes: 29 additions & 22 deletions schemas/pointer.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,28 +65,35 @@ examples:
- .length: $this
length: $wordsize

- # example `struct Record { uint128 x; uint128 y }` in memory
group:
- name: "struct-start"
location: stack
slot: 0

- name: "struct-member-0"
location: memory
# the first struct member begins at the offset indicated by the value
# at "struct-start"
offset:
$read: "struct-start"
length: $wordsize

- name: "struct-member-1"
location: memory
# the second struct member immediately follows the first
offset:
$sum:
- .offset: "struct-member-0"
- .length: "struct-member-0"
length: $wordsize
- # 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

- # example `(struct Record { uint256 x; uint256 y; })[] memory`
group:
Expand Down

0 comments on commit ed67237

Please sign in to comment.