Skip to content

Commit

Permalink
Merge pull request #8243 from ethereum-optimism/inphi/snapshots
Browse files Browse the repository at this point in the history
feat(ctb): add storage-layout and method ABI snapshots
  • Loading branch information
tynes authored Nov 27, 2023
2 parents 03ad50f + 23906bc commit ec6a2bd
Show file tree
Hide file tree
Showing 119 changed files with 17,597 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,16 @@ jobs:
pnpm autogen:invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: abi snapshot
command: |
pnpm abi-snapshot
if [ "$(git diff --exit-code snapshots)" ]; then
[ -z "$(git ls-files --others --exclude-standard snapshots)" ] || echo "export ABI_SNAPSHOT_STATUS=1" >> "$BASH_ENV"
else
echo "export ABI_SNAPSHOT_STATUS=1" >> "$BASH_ENV"
fi
working_directory: packages/contracts-bedrock
- run:
name: check statuses
command: |
Expand Down Expand Up @@ -459,6 +469,10 @@ jobs:
echo "Deploy config check failed, see job output for details."
FAILED=1
fi
if [[ "$ABI_SNAPSHOT_STATUS" -ne 0 ]]; then
echo "ABI snapshot check failed, see job output for details."
FAILED=1
fi
if [[ "$FAILED" -ne 0 ]]; then
exit 1
fi
Expand Down
1 change: 1 addition & 0 deletions packages/contracts-bedrock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"gas-snapshot:no-build": "forge snapshot --match-contract GasBenchMark",
"gas-snapshot": "pnpm build:go-ffi && pnpm gas-snapshot:no-build",
"storage-snapshot": "./scripts/storage-snapshot.sh",
"abi-snapshot": "npx tsx scripts/generate-snapshots.ts",
"semver-lock": "forge script scripts/SemverLock.s.sol",
"validate-deploy-configs": "./scripts/check-deploy-configs.sh",
"validate-spacers:no-build": "npx tsx scripts/validate-spacers.ts",
Expand Down
136 changes: 136 additions & 0 deletions packages/contracts-bedrock/scripts/generate-snapshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import fs from 'fs'
import path from 'path'

const outdir = process.argv[2] || path.join(__dirname, '..', 'snapshots')
const forgeArtifactsDir = path.join(__dirname, '..', 'forge-artifacts')

const getAllContracts = (): Array<string> => {
const paths = []
const readFilesRecursively = (dir: string) => {
const files = fs.readdirSync(dir)

for (const file of files) {
const filePath = path.join(dir, file)
const fileStat = fs.statSync(filePath)

if (fileStat.isDirectory()) {
readFilesRecursively(filePath)
} else {
paths.push(filePath)
}
}
}
readFilesRecursively(path.join(__dirname, '..', 'src'))

// Assumes there is a single contract per file
return paths
.filter((x) => x.endsWith('.sol'))
.map((p: string) => {
const b = path.basename(p)
return `${b}:${b.replace('.sol', '')}`
})
.sort()
}

type AbiSpecStorageLayoutEntry = {
label: string
slot: number
offset: number
type: string
bytes: number
}
const sortKeys = (obj: any) => {
if (typeof obj !== 'object' || obj === null) {
return obj
}
return Object.keys(obj)
.sort()
.reduce(
(acc, key) => {
acc[key] = sortKeys(obj[key])
return acc
},
Array.isArray(obj) ? [] : {}
)
}

const main = async () => {
console.log(`writing abi spec to ${outdir}`)

const storageLayoutDir = path.join(outdir, 'storageLayout')
const abiDir = path.join(outdir, 'abi')
fs.mkdirSync(storageLayoutDir, { recursive: true })
fs.mkdirSync(abiDir, { recursive: true })

const contracts = getAllContracts()

for (const contract of contracts) {
const toks = contract.split(':')
const contractFile = contract.split(':')[0]
const contractName = toks[1]

let artifactFile = path.join(
forgeArtifactsDir,
contractFile,
`${contractName}.json`
)

// NOTE: Read the first version in the directory. We may want to assert that all version's ABIs are identical
if (!fs.existsSync(artifactFile)) {
const filename = fs.readdirSync(path.dirname(artifactFile))[0]
artifactFile = path.join(path.dirname(artifactFile), filename)
}

const data = fs.readFileSync(artifactFile)
const artifact = JSON.parse(data.toString())

// ignore abstract contracts
if (artifact.bytecode.object === '0x') {
console.log(`ignoring interface ${contractName}`)
continue
}

// HACK: This is a hack to ignore libraries. Not robust against changes to solc's internal ast repr
const isContract = artifact.ast.nodes.some((node: any) => {
return (
node.nodeType === 'ContractDefinition' &&
node.name === contractName &&
node.contractKind === 'contract'
)
})
if (!isContract) {
console.log(`ignoring library/interface ${contractName}`)
continue
}

const storageLayout: AbiSpecStorageLayoutEntry[] = []
for (const storageEntry of artifact.storageLayout.storage) {
// convert ast-based type to solidity type
const typ = artifact.storageLayout.types[storageEntry.type]
if (typ === undefined) {
throw new Error(
`undefined type for ${contractName}:${storageEntry.label}`
)
}
storageLayout.push({
label: typ.label,
bytes: typ.numberOfBytes,
offset: storageEntry.offset,
slot: storageEntry.slot,
type: storageEntry.type,
})
}

// Sort snapshots for easier manual inspection
fs.writeFileSync(
`${abiDir}/${contractName}.json`,
JSON.stringify(sortKeys(artifact.abi), null, 2)
)
fs.writeFileSync(
`${storageLayoutDir}/${contractName}.json`,
JSON.stringify(sortKeys(storageLayout), null, 2)
)
}
}

main()
116 changes: 116 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/AddressManager.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "address",
"name": "newAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "oldAddress",
"type": "address"
}
],
"name": "AddressSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
}
],
"name": "getAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "address",
"name": "_address",
"type": "address"
}
],
"name": "setAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "_admin",
"type": "address"
},
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_version",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ADMIN",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PROOF_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address payable",
"name": "recipient",
"type": "address"
},
{
"internalType": "bytes32",
"name": "nonce",
"type": "bytes32"
}
],
"internalType": "struct Faucet.DripParameters",
"name": "_params",
"type": "tuple"
},
{
"internalType": "bytes32",
"name": "_id",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
}
],
"name": "verify",
"outputs": [
{
"internalType": "bool",
"name": "valid_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
Loading

0 comments on commit ec6a2bd

Please sign in to comment.