github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/scripts/autogen/generate-snapshots.ts (about)

     1  import fs from 'fs'
     2  import path from 'path'
     3  
     4  const root = path.join(__dirname, '..', '..')
     5  const outdir = process.argv[2] || path.join(root, 'snapshots')
     6  const forgeArtifactsDir = path.join(root, 'forge-artifacts')
     7  
     8  const getAllContractsSources = (): Array<string> => {
     9    const paths = []
    10    const readFilesRecursively = (dir: string) => {
    11      const files = fs.readdirSync(dir)
    12  
    13      for (const file of files) {
    14        const filePath = path.join(dir, file)
    15        const fileStat = fs.statSync(filePath)
    16  
    17        if (fileStat.isDirectory()) {
    18          readFilesRecursively(filePath)
    19        } else {
    20          paths.push(filePath)
    21        }
    22      }
    23    }
    24    readFilesRecursively(path.join(root, 'src'))
    25  
    26    return paths
    27      .filter((x) => x.endsWith('.sol'))
    28      .map((p: string) => path.basename(p))
    29      .sort()
    30  }
    31  
    32  type ForgeArtifact = {
    33    abi: object
    34    ast: {
    35      nodeType: string
    36      nodes: any[]
    37    }
    38    storageLayout: {
    39      storage: [{ type: string; label: string; offset: number; slot: number }]
    40      types: { [key: string]: { label: string; numberOfBytes: number } }
    41    }
    42    bytecode: {
    43      object: string
    44    }
    45  }
    46  
    47  type AbiSpecStorageLayoutEntry = {
    48    label: string
    49    slot: number
    50    offset: number
    51    bytes: number
    52    type: string
    53  }
    54  const sortKeys = (obj: any) => {
    55    if (typeof obj !== 'object' || obj === null) {
    56      return obj
    57    }
    58    return Object.keys(obj)
    59      .sort()
    60      .reduce(
    61        (acc, key) => {
    62          acc[key] = sortKeys(obj[key])
    63          return acc
    64        },
    65        Array.isArray(obj) ? [] : {}
    66      )
    67  }
    68  
    69  // ContractName.0.9.8.json -> ContractName.sol
    70  // ContractName.json -> ContractName.sol
    71  const parseArtifactName = (artifactVersionFile: string): string => {
    72    const match = artifactVersionFile.match(/(.*?)\.([0-9]+\.[0-9]+\.[0-9]+)?/)
    73    if (!match) {
    74      throw new Error(`Invalid artifact file name: ${artifactVersionFile}`)
    75    }
    76    return match[1]
    77  }
    78  
    79  const main = async () => {
    80    console.log(`writing abi and storage layout snapshots to ${outdir}`)
    81  
    82    const storageLayoutDir = path.join(outdir, 'storageLayout')
    83    const abiDir = path.join(outdir, 'abi')
    84    fs.mkdirSync(storageLayoutDir, { recursive: true })
    85    fs.mkdirSync(abiDir, { recursive: true })
    86  
    87    const contractSources = getAllContractsSources()
    88    const knownAbis = {}
    89  
    90    for (const contractFile of contractSources) {
    91      const contractArtifacts = path.join(forgeArtifactsDir, contractFile)
    92      for (const name of fs.readdirSync(contractArtifacts)) {
    93        const data = fs.readFileSync(path.join(contractArtifacts, name))
    94        const artifact: ForgeArtifact = JSON.parse(data.toString())
    95  
    96        const contractName = parseArtifactName(name)
    97  
    98        // HACK: This is a hack to ignore libraries and abstract contracts. Not robust against changes to solc's internal ast repr
    99        const isContract = artifact.ast.nodes.some((node: any) => {
   100          return (
   101            node.nodeType === 'ContractDefinition' &&
   102            node.name === contractName &&
   103            node.contractKind === 'contract' &&
   104            (node.abstract === undefined || // solc < 0.6 doesn't have explicit abstract contracts
   105              node.abstract === false)
   106          )
   107        })
   108        if (!isContract) {
   109          console.log(`ignoring library/interface ${contractName}`)
   110          continue
   111        }
   112  
   113        const storageLayout: AbiSpecStorageLayoutEntry[] = []
   114        for (const storageEntry of artifact.storageLayout.storage) {
   115          // convert ast-based type to solidity type
   116          const typ = artifact.storageLayout.types[storageEntry.type]
   117          if (typ === undefined) {
   118            throw new Error(
   119              `undefined type for ${contractName}:${storageEntry.label}`
   120            )
   121          }
   122          storageLayout.push({
   123            label: storageEntry.label,
   124            bytes: typ.numberOfBytes,
   125            offset: storageEntry.offset,
   126            slot: storageEntry.slot,
   127            type: typ.label,
   128          })
   129        }
   130  
   131        if (knownAbis[contractName] === undefined) {
   132          knownAbis[contractName] = artifact.abi
   133        } else if (
   134          JSON.stringify(knownAbis[contractName]) !== JSON.stringify(artifact.abi)
   135        ) {
   136          throw Error(
   137            `detected multiple artifact versions with different ABIs for ${contractFile}`
   138          )
   139        } else {
   140          console.log(`detected multiple artifacts for ${contractName}`)
   141        }
   142  
   143        // Sort snapshots for easier manual inspection
   144        fs.writeFileSync(
   145          `${abiDir}/${contractName}.json`,
   146          JSON.stringify(sortKeys(artifact.abi), null, 2)
   147        )
   148        fs.writeFileSync(
   149          `${storageLayoutDir}/${contractName}.json`,
   150          JSON.stringify(sortKeys(storageLayout), null, 2)
   151        )
   152      }
   153    }
   154  }
   155  
   156  main()