1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { task } from 'hardhat/config'
task(
'validate-spacers',
'validates that spacer variables are in the correct positions'
).setAction(async (args, hre) => {
const names = await hre.artifacts.getAllFullyQualifiedNames()
for (const name of names) {
// Script is remarkably slow because of getBuildInfo, so better to skip test files since they
// don't matter for this check.
if (name.includes('.t.sol')) {
continue
}
const buildInfo = await hre.artifacts.getBuildInfo(name)
for (const source of Object.values(buildInfo.output.contracts)) {
for (const [contractName, contract] of Object.entries(source)) {
const storageLayout = (contract as any).storageLayout
for (const variable of storageLayout?.storage || []) {
if (variable.label.startsWith('spacer_')) {
const [, slot, offset, length] = variable.label.split('_')
// Check that the slot is correct.
if (slot !== variable.slot) {
throw new Error(
`${contractName} ${variable.label} is in slot ${variable.slot} but should be in ${slot}`
)
}
// Check that the offset is correct.
if (parseInt(offset, 10) !== variable.offset) {
throw new Error(
`${contractName} ${variable.label} is at offset ${variable.offset} but should be at ${offset}`
)
}
// Figure out the length of the variable.
let variableLength: number
if (variable.type.startsWith('t_mapping')) {
variableLength = 32
} else if (variable.type.startsWith('t_uint')) {
variableLength = variable.type.match(/uint([0-9]+)/)?.[1] / 8
} else if (variable.type.startsWith('t_bytes_')) {
variableLength = 32
} else if (variable.type.startsWith('t_bytes')) {
variableLength = variable.type.match(/uint([0-9]+)/)?.[1]
} else if (variable.type.startsWith('t_address')) {
variableLength = 20
} else {
throw new Error('unsupported type, add it to the script')
}
// Check that the length is correct.
if (parseInt(length, 10) !== variableLength) {
throw new Error(
`${contractName} ${variable.label} is ${variableLength} bytes long but should be ${length}`
)
}
}
}
}
}
}
})