Commit b50bb51e authored by Mark Tyneway's avatar Mark Tyneway

ctb: refactor layout script

parent 41902810
...@@ -5,6 +5,31 @@ ...@@ -5,6 +5,31 @@
"offset": 0, "offset": 0,
"length": 20 "length": 20
}, },
"spacer_1_0_1600": {
"slot": 1,
"offset": 0,
"length": 1600
},
"spacer_51_0_20": {
"slot": 51,
"offset": 0,
"length": 20
},
"spacer_52_0_1568": {
"slot": 52,
"offset": 0,
"length": 1568
},
"spacer_101_0_1": {
"slot": 101,
"offset": 0,
"length": 1
},
"spacer_102_0_1568": {
"slot": 102,
"offset": 0,
"length": 1568
},
"spacer_151_0_32": { "spacer_151_0_32": {
"slot": 151, "slot": 151,
"offset": 0, "offset": 0,
...@@ -27,6 +52,31 @@ ...@@ -27,6 +52,31 @@
"offset": 0, "offset": 0,
"length": 20 "length": 20
}, },
"spacer_1_0_1600": {
"slot": 1,
"offset": 0,
"length": 1600
},
"spacer_51_0_20": {
"slot": 51,
"offset": 0,
"length": 20
},
"spacer_52_0_1568": {
"slot": 52,
"offset": 0,
"length": 1568
},
"spacer_101_0_1": {
"slot": 101,
"offset": 0,
"length": 1
},
"spacer_102_0_1568": {
"slot": 102,
"offset": 0,
"length": 1568
},
"spacer_151_0_32": { "spacer_151_0_32": {
"slot": 151, "slot": 151,
"offset": 0, "offset": 0,
......
import { task } from 'hardhat/config' import { task } from 'hardhat/config'
import { parseFullyQualifiedName } from 'hardhat/utils/contract-names'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import layoutLock from '../layout-lock.json' import layoutLock from '../layout-lock.json'
// Artifacts that should be skipped when inspecting their storage layout
const skipped = {
// Both of these are skipped because they are meant to be inherited
// by the CrossDomainMessenger. It is the CrossDomainMessenger that
// should be inspected, not these contracts.
CrossDomainMessengerLegacySpacer0: true,
CrossDomainMessengerLegacySpacer1: true,
}
/** /**
* Parses out variable info from the variable structure in standard compiler json output. * Parses out variable info from the variable structure in standard compiler json output.
* *
...@@ -30,8 +41,23 @@ const parseVariableInfo = ( ...@@ -30,8 +41,23 @@ const parseVariableInfo = (
variableLength = 20 variableLength = 20
} else if (variable.type.startsWith('t_bool')) { } else if (variable.type.startsWith('t_bool')) {
variableLength = 1 variableLength = 1
} else if (variable.type.startsWith('t_array')) {
// Figure out the size of the type inside of the array
// and then multiply that by the length of the array.
// This does not support recursion multiple times for simplicity
const type = variable.type.match(/^t_array\((\w+)\)/)?.[1]
const info = parseVariableInfo({
label: variable.label,
offset: variable.offset,
slot: variable.slot,
type,
})
const size = variable.type.match(/^t_array\(\w+\)([0-9]+)/)?.[1]
variableLength = info.length * parseInt(size, 10)
} else { } else {
throw new Error('unsupported type, add it to the script') throw new Error(
`${variable.label}: unsupported type ${variable.type}, add it to the script`
)
} }
return { return {
...@@ -45,31 +71,50 @@ const parseVariableInfo = ( ...@@ -45,31 +71,50 @@ const parseVariableInfo = (
task( task(
'validate-spacers', 'validate-spacers',
'validates that spacer variables are in the correct positions' 'validates that spacer variables are in the correct positions'
).setAction(async (args, hre) => { ).setAction(async ({}, hre: HardhatRuntimeEnvironment) => {
const accounted: string[] = [] const accounted: string[] = []
const names = await hre.artifacts.getAllFullyQualifiedNames() const names = await hre.artifacts.getAllFullyQualifiedNames()
for (const name of names) { for (const fqn of names) {
// Script is remarkably slow because of getBuildInfo, so better to skip test files since they // Script is remarkably slow because of getBuildInfo, so better to skip test files since they
// don't matter for this check. // don't matter for this check.
if (name.includes('.t.sol')) { if (fqn.includes('.t.sol')) {
continue
}
console.log(`Processing ${fqn}`)
const parsed = parseFullyQualifiedName(fqn)
const contractName = parsed.contractName
if (skipped[contractName] === true) {
console.log(`Skipping ${contractName} because it is marked as skippable`)
continue continue
} }
// Some files may not have buildInfo (certain libraries). We can safely skip these because we // Some files may not have buildInfo (certain libraries). We can safely skip these because we
// make sure that everything is accounted for anyway. // make sure that everything is accounted for anyway.
const buildInfo = await hre.artifacts.getBuildInfo(name) const buildInfo = await hre.artifacts.getBuildInfo(fqn)
if (buildInfo === undefined) { if (buildInfo === undefined) {
console.log(`Skipping ${name} because it has no buildInfo`) console.log(`Skipping ${fqn} because it has no buildInfo`)
continue continue
} }
for (const source of Object.values(buildInfo.output.contracts)) { const sources = buildInfo.output.contracts
for (const [contractName, contract] of Object.entries(source)) { for (const [sourceName, source] of Object.entries(sources)) {
// The source file may have our contract
if (sourceName.includes(parsed.sourceName)) {
const contract = source[contractName]
if (!contract) {
console.log(`Skipping ${contractName} as its not found in the source`)
continue
}
const storageLayout = (contract as any).storageLayout const storageLayout = (contract as any).storageLayout
if (!storageLayout) {
continue
}
// Check that the layout lock is respected.
if (layoutLock[contractName]) { if (layoutLock[contractName]) {
console.log(`Processing layout lock for ${contractName}`)
const removed = Object.entries(layoutLock[contractName]).filter( const removed = Object.entries(layoutLock[contractName]).filter(
([key, val]: any) => { ([key, val]: any) => {
const storage = storageLayout?.storage || [] const storage = storageLayout?.storage || []
...@@ -82,6 +127,7 @@ task( ...@@ -82,6 +127,7 @@ task(
// Make sure the variable matches **exactly**. // Make sure the variable matches **exactly**.
const variableInfo = parseVariableInfo(item) const variableInfo = parseVariableInfo(item)
return ( return (
variableInfo.name === key && variableInfo.name === key &&
variableInfo.offset === val.offset && variableInfo.offset === val.offset &&
...@@ -97,11 +143,10 @@ task( ...@@ -97,11 +143,10 @@ task(
`variable(s) removed from ${contractName}: ${removed.join(', ')}` `variable(s) removed from ${contractName}: ${removed.join(', ')}`
) )
} }
console.log(`Valid layout lock for ${contractName}`)
accounted.push(contractName) accounted.push(contractName)
} }
// Check that the positions have not changed.
for (const variable of storageLayout?.storage || []) { for (const variable of storageLayout?.storage || []) {
if (variable.label.startsWith('spacer_')) { if (variable.label.startsWith('spacer_')) {
const [, slot, offset, length] = variable.label.split('_') const [, slot, offset, length] = variable.label.split('_')
...@@ -127,6 +172,8 @@ task( ...@@ -127,6 +172,8 @@ task(
`${contractName} ${variable.label} is ${variableInfo.length} bytes long but should be ${length}` `${contractName} ${variable.label} is ${variableInfo.length} bytes long but should be ${length}`
) )
} }
console.log(`${contractName}.${variable.label} is valid`)
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment