generate-snapshots.ts 3.69 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 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
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
27
    .filter((x) => x.endsWith('.sol'))
28 29 30 31
    .map((p: string) => {
      const b = path.basename(p)
      return `${b}:${b.replace('.sol', '')}`
    })
32 33 34 35
    .sort()
}

type AbiSpecStorageLayoutEntry = {
inphi's avatar
inphi committed
36
  label: string
37 38
  slot: number
  offset: number
inphi's avatar
inphi committed
39
  type: string
40
  bytes: number
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
}
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}`)
inphi's avatar
inphi committed
59 60 61 62 63

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

  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
    }

93
    // HACK: This is a hack to ignore libraries. Not robust against changes to solc's internal ast repr
inphi's avatar
inphi committed
94 95
    const isContract = artifact.ast.nodes.some((node: any) => {
      return (
96 97
        node.nodeType === 'ContractDefinition' &&
        node.name === contractName &&
inphi's avatar
inphi committed
98 99
        node.contractKind === 'contract'
      )
100 101 102 103 104 105
    })
    if (!isContract) {
      console.log(`ignoring library/interface ${contractName}`)
      continue
    }

inphi's avatar
inphi committed
106
    const storageLayout: AbiSpecStorageLayoutEntry[] = []
107
    for (const storageEntry of artifact.storageLayout.storage) {
108 109 110
      // convert ast-based type to solidity type
      const typ = artifact.storageLayout.types[storageEntry.type]
      if (typ === undefined) {
inphi's avatar
inphi committed
111 112 113
        throw new Error(
          `undefined type for ${contractName}:${storageEntry.label}`
        )
114
      }
inphi's avatar
inphi committed
115
      storageLayout.push({
116 117
        label: typ.label,
        bytes: typ.numberOfBytes,
118
        offset: storageEntry.offset,
inphi's avatar
inphi committed
119 120 121
        slot: storageEntry.slot,
        type: storageEntry.type,
      })
122 123
    }

inphi's avatar
inphi committed
124 125 126 127 128
    // Sort snapshots for easier manual inspection
    fs.writeFileSync(
      `${abiDir}/${contractName}.json`,
      JSON.stringify(sortKeys(artifact.abi), null, 2)
    )
129
    fs.writeFileSync(
inphi's avatar
inphi committed
130 131
      `${storageLayoutDir}/${contractName}.json`,
      JSON.stringify(sortKeys(storageLayout), null, 2)
132 133 134 135 136
    )
  }
}

main()