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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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 getAllContractsSources = (): 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'))
return paths
.filter((x) => x.endsWith('.sol'))
.map((p: string) => path.basename(p))
.sort()
}
type ForgeArtifact = {
abi: object
ast: {
nodeType: string
nodes: any[]
}
storageLayout: {
storage: [{ type: string; label: string; offset: number; slot: number }]
types: { [key: string]: { label: string; numberOfBytes: number } }
}
bytecode: {
object: string
}
}
type AbiSpecStorageLayoutEntry = {
label: string
slot: number
offset: number
bytes: number
type: string
}
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) ? [] : {}
)
}
// ContractName.0.9.8.json -> ContractName.sol
// ContractName.json -> ContractName.sol
const parseArtifactName = (artifactVersionFile: string): string => {
const match = artifactVersionFile.match(/(.*?)\.([0-9]+\.[0-9]+\.[0-9]+)?/)
if (!match) {
throw new Error(`Invalid artifact file name: ${artifactVersionFile}`)
}
return match[1]
}
const main = async () => {
console.log(`writing abi and storage layout snapshots 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 contractSources = getAllContractsSources()
const knownAbis = {}
for (const contractFile of contractSources) {
const contractArtifacts = path.join(forgeArtifactsDir, contractFile)
for (const name of fs.readdirSync(contractArtifacts)) {
const data = fs.readFileSync(path.join(contractArtifacts, name))
const artifact: ForgeArtifact = JSON.parse(data.toString())
const contractName = parseArtifactName(name)
// HACK: This is a hack to ignore libraries and abstract contracts. 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' &&
(node.abstract === undefined || // solc < 0.6 doesn't have explicit abstract contracts
node.abstract === false)
)
})
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: storageEntry.label,
bytes: typ.numberOfBytes,
offset: storageEntry.offset,
slot: storageEntry.slot,
type: typ.label,
})
}
if (knownAbis[contractName] === undefined) {
knownAbis[contractName] = artifact.abi
} else if (
JSON.stringify(knownAbis[contractName]) !== JSON.stringify(artifact.abi)
) {
throw Error(
`detected multiple artifact versions with different ABIs for ${contractFile}`
)
} else {
console.log(`detected multiple artifacts for ${contractName}`)
}
// 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()