generate-snapshots.ts 4.48 KB
Newer Older
1 2 3 4 5 6
import fs from 'fs'
import path from 'path'

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

7
const getAllContractsSources = (): Array<string> => {
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
  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
26
    .filter((x) => x.endsWith('.sol'))
27
    .map((p: string) => path.basename(p))
28 29 30
    .sort()
}

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
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
  }
}

46
type AbiSpecStorageLayoutEntry = {
inphi's avatar
inphi committed
47
  label: string
48 49
  slot: number
  offset: number
50
  bytes: number
51
  type: string
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
}
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) ? [] : {}
    )
}

68 69 70
// ContractName.0.9.8.json -> ContractName.sol
// ContractName.json -> ContractName.sol
const parseArtifactName = (artifactVersionFile: string): string => {
inphi's avatar
inphi committed
71
  const match = artifactVersionFile.match(/(.*?)\.([0-9]+\.[0-9]+\.[0-9]+)?/)
72
  if (!match) {
inphi's avatar
inphi committed
73
    throw new Error(`Invalid artifact file name: ${artifactVersionFile}`)
74
  }
inphi's avatar
inphi committed
75
  return match[1]
76 77
}

78
const main = async () => {
79
  console.log(`writing abi and storage layout snapshots to ${outdir}`)
inphi's avatar
inphi committed
80 81 82 83 84

  const storageLayoutDir = path.join(outdir, 'storageLayout')
  const abiDir = path.join(outdir, 'abi')
  fs.mkdirSync(storageLayoutDir, { recursive: true })
  fs.mkdirSync(abiDir, { recursive: true })
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
  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
      }
111

112 113 114 115 116 117 118 119 120 121 122 123 124 125
      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,
126
          type: typ.label,
127 128
        })
      }
129

130 131 132 133 134 135 136
      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}`
inphi's avatar
inphi committed
137
        )
138 139
      } else {
        console.log(`detected multiple artifacts for ${contractName}`)
140
      }
141

142 143 144 145 146 147 148 149 150 151
      // 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)
      )
    }
152 153 154 155
  }
}

main()