Commit 561161a4 authored by Mark Tyneway's avatar Mark Tyneway

test: add erc20 test coverage

parent 87c4dc7e
......@@ -17,6 +17,7 @@
"@discoveryjs/json-ext": "^0.5.3",
"@eth-optimism/core-utils": "0.6.0",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abi": "^5.5.0",
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/properties": "^5.5.0",
"@ethersproject/providers": "^5.5.0",
......@@ -45,7 +46,7 @@
"mocha": "^9.1.2",
"node-fetch": "2.6.5",
"solc": "0.8.7-fixed",
"ts-node": "^10.0.0",
"ts-mocha": "^8.0.0"
"ts-mocha": "^8.0.0",
"ts-node": "^10.0.0"
}
}
......@@ -13,7 +13,7 @@ import {
DELETE_CONTRACTS,
} from './constants'
import { Account, AccountType, SurgeryDataSources } from './types'
import { hexStringEqual } from './utils'
import { hexStringEqual, isBytecodeERC20 } from './utils'
export const classifiers: {
[key in AccountType]: (account: Account, data: SurgeryDataSources) => boolean
......@@ -90,6 +90,9 @@ export const classifiers: {
[AccountType.VERIFIED]: (account, data) => {
return !classifiers[AccountType.UNVERIFIED](account, data)
},
[AccountType.ERC20]: (account) => {
return isBytecodeERC20(account.code)
},
}
export const classify = (
......
......@@ -434,4 +434,9 @@ export const handlers: {
code: bytecode,
}
},
[AccountType.ERC20]: async (account) => {
throw new Error(
`Unexpected ERC20 classification, this should never happen: ${account.address}`
)
},
}
......@@ -59,6 +59,7 @@ export enum AccountType {
UNISWAP_V3_OTHER,
UNVERIFIED,
VERIFIED,
ERC20,
}
export interface UniswapPoolData {
......
/* eslint @typescript-eslint/no-var-requires: "off" */
import { ethers } from 'ethers'
import { abi as UNISWAP_FACTORY_ABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json'
import { Interface } from '@ethersproject/abi'
import { parseChunked } from '@discoveryjs/json-ext'
import { createReadStream } from 'fs'
import * as fs from 'fs'
import byline from 'byline'
import * as dotenv from 'dotenv'
import * as assert from 'assert'
import { reqenv, getenv } from '@eth-optimism/core-utils'
import { reqenv, getenv, remove0x } from '@eth-optimism/core-utils'
import {
Account,
EtherscanContract,
......@@ -114,6 +115,48 @@ export const getMappingKey = (keys: any[], slot: number) => {
return key
}
// ERC20 interface
const iface = new Interface([
'function balanceOf(address)',
'function name()',
'function symbol()',
'function decimals()',
'function totalSupply()',
'function transfer(address,uint256)',
])
// PUSH4 should prefix any 4 byte selector
const PUSH4 = 0x63
const erc20Sighashes = new Set()
// Build the set of erc20 4 byte selectors
for (const fn of Object.keys(iface.functions)) {
const sighash = iface.getSighash(fn)
erc20Sighashes.add(sighash)
}
export const isBytecodeERC20 = (bytecode: string): boolean => {
if (bytecode === '0x' || bytecode === undefined) {
return false
}
const seen = new Set()
const buf = Buffer.from(remove0x(bytecode), 'hex')
for (const [i, byte] of buf.entries()) {
// Track all of the observed 4 byte selectors that follow a PUSH4
// and are also present in the set of erc20Sighashes
if (byte === PUSH4) {
const sighash = '0x' + buf.slice(i + 1, i + 5).toString('hex')
if (erc20Sighashes.has(sighash)) {
seen.add(sighash)
}
}
}
// create a set that contains those elements of set
// erc20Sighashes that are not in set seen
const elements = [...erc20Sighashes].filter((x) => !seen.has(x))
return !elements.length
}
export const getUniswapV3Factory = (signerOrProvider: any): ethers.Contract => {
return new ethers.Contract(
UNISWAP_V3_FACTORY_ADDRESS,
......
import { expect } from '@eth-optimism/core-utils/test/setup'
import { BigNumber } from 'ethers'
import { env } from './setup'
describe('erc20', () => {
describe('standard ERC20', () => {
before(async () => {
await env.init()
})
it('ERC20s', () => {
for (const [i, erc20] of env.erc20s.entries()) {
describe(`erc20 ${i}/${env.erc20s.length} (${erc20.address})`, () => {
it('should have the same storage', async () => {
const account = env.surgeryDataSources.dump.find(
(a) => a.address === erc20.address
)
if (account.storage) {
for (const key of Object.keys(account.storage)) {
const pre = await env.preL2Provider.getStorageAt(
account.address,
BigNumber.from(key)
)
const post = await env.postL2Provider.getStorageAt(
account.address,
BigNumber.from(key)
)
expect(pre).to.deep.eq(post)
}
}
})
})
}
})
})
})
......@@ -49,7 +49,7 @@ const genesis: Genesis = {
},
}
describe.only('GenesisJsonProvider', () => {
describe('GenesisJsonProvider', () => {
let provider
before(() => {
provider = new GenesisJsonProvider(genesis)
......
......@@ -7,7 +7,7 @@ import { getenv, remove0x } from '@eth-optimism/core-utils'
import { providers, BigNumber } from 'ethers'
import { SurgeryDataSources, Account, AccountType } from '../scripts/types'
import { loadSurgeryData } from '../scripts/data'
import { classify } from '../scripts/classifiers'
import { classify, classifiers } from '../scripts/classifiers'
import { GenesisJsonProvider } from './provider'
// Chai plugins go here.
......@@ -64,6 +64,9 @@ class TestEnv {
// List of typed accounts in the input dump
accounts: TypedAccount[] = []
// List of erc20 contracts in input dump
erc20s: Account[] = []
constructor(opts: TestEnvConfig) {
this.config = opts
// If the pre provider url is provided, use a json rpc provider.
......@@ -138,6 +141,10 @@ class TestEnv {
...account,
type: accountType,
})
if (classifiers[AccountType.ERC20](account, this.surgeryDataSources)) {
this.erc20s.push(account)
}
}
}
}
......
import { expect } from '@eth-optimism/core-utils/test/setup'
import fs from 'fs/promises'
import path from 'path'
import { isBytecodeERC20 } from '../scripts/utils'
describe('Utils', () => {
// Read in the mock data
const contracts = {}
before(async () => {
const files = await fs.readdir(path.join(__dirname, 'data'))
for (const filename of files) {
const file = await fs.readFile(path.join(__dirname, 'data', filename))
const name = path.parse(filename).name
const json = JSON.parse(file.toString())
contracts[name] = {
bytecode: json.bytecode.toString().trim(),
expected: json.expected,
}
}
})
it('isBytecodeERC20', () => {
for (const [name, contract] of Object.entries(contracts)) {
describe(`contract ${name}`, () => {
it('should be identified erc20', () => {
const result = isBytecodeERC20((contract as any).bytecode as string)
expect(result).to.eq((contract as any).expected)
})
})
}
})
})
......@@ -734,6 +734,21 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/abi@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613"
integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==
dependencies:
"@ethersproject/address" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/constants" "^5.5.0"
"@ethersproject/hash" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/abstract-provider@5.4.1", "@ethersproject/abstract-provider@^5.0.0", "@ethersproject/abstract-provider@^5.4.0", "@ethersproject/abstract-provider@^5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz#e404309a29f771bd4d28dbafadcaa184668c2a6e"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment