Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/contracts/bin/make-dump.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* External Imports */
import * as fs from 'fs'
import * as path from 'path'
import * as mkdirp from 'mkdirp'

/* Internal Imports */
import { makeStateDump } from '../src'
;(async () => {
const outdir = path.resolve(__dirname, '../build/dumps')
const outfile = path.join(outdir, 'state-dump.latest.json')
mkdirp.sync(outdir)

const dump = await makeStateDump()
fs.writeFileSync(outfile, JSON.stringify(dump))
})()
10 changes: 7 additions & 3 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"files": [
"build/**/*.js",
"build/contracts/*",
"build/dumps/*json",
"build/artifacts/*json"
],
"license": "MIT",
Expand All @@ -27,23 +28,24 @@
"test:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST,FLAG_IS_DEBUG\" buidler test --show-stack-traces",
"coverage": "yarn run coverage:contracts",
"coverage:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST\" buidler coverage --network coverage --show-stack-traces --testfiles \"test/contracts/**/*.spec.ts\"",
"build": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy",
"build": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy && yarn run build:dump",
"build:contracts": "buidler compile",
"build:typescript": "tsc -p .",
"build:copy": "yarn run build:copy:contracts",
"build:copy:contracts": "copyfiles -u 2 \"contracts/optimistic-ethereum/**/*.sol\" \"build/contracts\"",
"build:dump": "ts-node \"./bin/make-dump.ts\"",
"clean": "rm -rf ./artifacts ./build ./cache",
"lint": "yarn run lint:typescript",
"lint:typescript": "tslint --format stylish --project .",
"fix": "yarn run fix:typescript",
"fix:typescript": "prettier --config ../../prettier-config.json --write \"index.ts\" \"buidler.config.ts\" \"{src,test,plugins}/**/*.ts\"",
"fix:typescript": "prettier --config ../../prettier-config.json --write \"index.ts\" \"buidler.config.ts\" \"{src,test,plugins,bin}/**/*.ts\"",
"deploy:all": "env DEBUG=\"info:*,error:*,debug:*\" node ./build/src/exec/deploy-contracts.js"
},
"dependencies": {
"@ethersproject/keccak256": "5.0.3",
"@eth-optimism/core-db": "^0.0.1-alpha.30",
"@eth-optimism/core-utils": "^0.0.1-alpha.30",
"@eth-optimism/solc": "^0.5.16-alpha.2",
"@ethersproject/keccak256": "5.0.3",
"@nomiclabs/buidler": "1.3.8",
"@nomiclabs/buidler-ethers": "^2.0.0",
"@nomiclabs/buidler-solpp": "^1.3.3",
Expand All @@ -67,7 +69,9 @@
"devDependencies": {
"copyfiles": "^2.3.0",
"cross-env": "^7.0.2",
"ganache-core": "^2.11.3",
"glob": "^7.1.6",
"mkdirp": "^1.0.4",
"solidity-coverage": "^0.7.9"
}
}
227 changes: 227 additions & 0 deletions packages/contracts/src/dump.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/* External Imports */
import * as path from 'path'
import { ethers } from 'ethers'
import * as Ganache from 'ganache-core'
import { deployAllContracts, RollupDeployConfig } from './deployment'
import { getContractDefinition } from './contract-imports'
import { keccak256 } from 'ethers/lib/utils'

type Accounts = Array<{
originalAddress: string
address: string
code: string
}>

interface StorageDump {
[key: string]: string
}

export interface StateDump {
contracts: {
ovmExecutionManager: string
ovmStateManager: string
}
accounts: {
[address: string]: {
balance: number
nonce: number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have codeHash as well, but it could be derived from the contract code. Removes the dependency of the consumer needing to have a keccak implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Will do.

code: string
storage: StorageDump
}
}
}

/**
* Finds the addresses of all accounts changed in the state.
* @param cStateManager Instance of the callback-based internal vm StateManager.
* @returns Array of changed addresses.
*/
const getChangedAccounts = async (cStateManager: any): Promise<string[]> => {
return new Promise<string[]>((resolve, reject) => {
const accounts: string[] = []
const stream = cStateManager._trie.createReadStream()

stream.on('data', (val: any) => {
accounts.push(val.key.toString('hex'))
})

stream.on('end', () => {
resolve(accounts)
})
})
}

/**
* Generates a storage dump for a given address.
* @param cStateManager Instance of the callback-based internal vm StateManager.
* @param address Address to generate a state dump for.
*/
const getStorageDump = async (
cStateManager: any,
address: string
): Promise<StorageDump> => {
return new Promise<StorageDump>((resolve, reject) => {
cStateManager._getStorageTrie(address, (err: any, trie: any) => {
if (err) {
reject(err)
}

const storage: StorageDump = {}
const stream = trie.createReadStream()

stream.on('data', (val: any) => {
storage[val.key.toString('hex')] = val.value.toString('hex')
})

stream.on('end', () => {
resolve(storage)
})
})
})
}

/**
* Replaces old addresses found in a storage dump with new ones.
* @param storageDump Storage dump to sanitize.
* @param accounts Set of accounts to sanitize with.
* @returns Sanitized storage dump.
*/
const sanitizeStorageDump = (
storageDump: StorageDump,
accounts: Accounts
): StorageDump => {
for (const [key, value] of Object.entries(storageDump)) {
let parsedKey = key
let parsedValue = value
for (const account of accounts) {
const re = new RegExp(`${account.originalAddress}`, 'g')
parsedValue = parsedValue.replace(re, account.address)
parsedKey = parsedKey.replace(re, account.address)
}

if (parsedKey !== key) {
delete storageDump[key]
}

storageDump[parsedKey] = parsedValue
}

return storageDump
}

export const makeStateDump = async (): Promise<any> => {
const ganache = Ganache.provider({
gasLimit: 100_000_000,
allowUnlimitedContractSize: true,
accounts: [
{
secretKey:
'0x29f3edee0ad3abf8e2699402e0e28cd6492c9be7eaab00d732a791c33552f797',
balance: 10000000000000000000000000000000000,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indentation off

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh rip, I think this is just prettier being funky.

},
],
})

const provider = new ethers.providers.Web3Provider(ganache)
const signer = provider.getSigner(0)

const config: RollupDeployConfig = {
signer,
rollupOptions: {
forceInclusionPeriodSeconds: 600,
ownerAddress: await signer.getAddress(),
sequencerAddress: await signer.getAddress(),
gasMeterConfig: {
ovmTxFlatGasFee: 0,
ovmTxMaxGas: 1_000_000_000,
gasRateLimitEpochLength: 600,
maxSequencedGasPerEpoch: 1_000_000_000_000,
maxQueuedGasPerEpoch: 1_000_000_000_000,
},
deployerWhitelistOwnerAddress: await signer.getAddress(),
allowArbitraryContractDeployment: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this option was removed from being useful since initialize(address,bool) needs to be called on the contract before the contract does anything, unless this will send an additional transaction initializing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm not actually sure if it's even used in the deployment, but it's currently a required deployment configuration option. Do you think better to update the deploy config interface? Or just keep the option here for now and do that in another PR?

},
}

const resolver = await deployAllContracts(config)

const pStateManager = ganache.engine.manager.state.blockchain.vm.pStateManager
const cStateManager = pStateManager._wrapped

const ovmExecutionManagerOriginalAddress = resolver.contracts.executionManager.address.slice(2).toLowerCase()
const ovmExecutionManagerAddress = 'c0dec0dec0dec0dec0dec0dec0dec0dec0de0000'

const ovmStateManagerOriginalAddress = resolver.contracts.stateManager.address.slice(2).toLowerCase()
const ovmStateManagerAddress = 'c0dec0dec0dec0dec0dec0dec0dec0dec0de0001'

const l2ToL1MessagePasserDef = getContractDefinition('L2ToL1MessagePasser')
const l2ToL1MessagePasserHash = keccak256(l2ToL1MessagePasserDef.deployedBytecode)
const l2ToL1MessagePasserAddress = '4200000000000000000000000000000000000000'

const l1MessageSenderDef = getContractDefinition('L1MessageSender')
const l1MessageSenderHash = keccak256(l1MessageSenderDef.deployedBytecode)
const l1MessageSenderAddress = '4200000000000000000000000000000000000001'

const changedAccounts = await getChangedAccounts(cStateManager)

let deadAddressIndex = 0
let accounts: Accounts = []

for (const originalAddress of changedAccounts) {
const code = (await pStateManager.getContractCode(originalAddress)).toString('hex')
const codeHash = keccak256('0x' + code)

if (code.length === 0) {
continue
}

// Sorry for this one!
let address = originalAddress
if (codeHash === l2ToL1MessagePasserHash) {
address = l2ToL1MessagePasserAddress
} else if (codeHash === l1MessageSenderHash) {
address = l1MessageSenderAddress
} else if (originalAddress === ovmExecutionManagerOriginalAddress) {
address = ovmExecutionManagerAddress
} else if (originalAddress === ovmStateManagerOriginalAddress) {
address = ovmStateManagerAddress
} else {
address = `deaddeaddeaddeaddeaddeaddeaddeaddead${deadAddressIndex.toString(16).padStart(4, '0')}`
deadAddressIndex++
}

accounts.push({
originalAddress,
address,
code: code
})
}

const dump: StateDump = {
contracts: {
ovmExecutionManager: '0x' + ovmExecutionManagerAddress,
ovmStateManager: '0x' + ovmStateManagerAddress,
},
accounts: {},
}

for (const account of accounts) {
const storageDump = sanitizeStorageDump(
await getStorageDump(cStateManager, account.originalAddress),
accounts
)

dump.accounts[account.address] = {
balance: 0,
nonce: 0,
code: account.code,
storage: storageDump
}
}

return dump
}

export const getLatestStateDump = (): StateDump => {
return require(path.join(__dirname, '../dumps', `state-dump.latest.json`))
}
1 change: 1 addition & 0 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './deployment'
export * from './contract-imports'
export * from './dump'
8 changes: 5 additions & 3 deletions packages/ovm-toolchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,16 @@
},
"dependencies": {
"@eth-optimism/core-utils": "^0.0.1-alpha.30",
"@eth-optimism/rollup-contracts": "^0.0.1-alpha.33",
"@eth-optimism/contracts": "^0.0.2-alpha.1",
"@eth-optimism/ethereumjs-vm": "4.2.0-alpha.0",
"@nomiclabs/buidler": "^1.4.4",
"bn.js": "^5.1.3",
"child_process": "^1.0.2",
"ethereum-waffle-v2": "npm:ethereum-waffle@2",
"ethereum-waffle-v3": "npm:ethereum-waffle@3",
"ethereumjs-ovm": "git+https://github.com/ethereum-optimism/ethereumjs-vm",
"ethers-v4": "npm:ethers@4",
"ethers-v5": "npm:ethers@5.0.7"
"ethers-v5": "npm:ethers@5.0.7",
"ethjs-common-v1": "npm:ethereumjs-common@1.5.0",
"ethjs-util-v6": "npm:ethereumjs-util@6.2.1"
}
}
29 changes: 18 additions & 11 deletions packages/ovm-toolchain/src/buidler-plugins/buidler-ovm-node.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import { extendEnvironment } from '@nomiclabs/buidler/config'
// tslint:disable-next-line
const VM = require('ethereumjs-ovm').default
// tslint:disable-next-line
const BN = require('bn.js')
import { extendEnvironment } from '@nomiclabs/buidler/config'

/* Internal Imports */
import { makeOVM } from '../utils/ovm'

extendEnvironment(async (bre) => {
const config: any = bre.config
config.startOvmNode = async (): Promise<void> => {
const gasLimit = 100_000_000
const ovmGasLimit = config.ovmGasLimit || 100_000_000

// Initialize the provider so it has a VM instance ready to copy.
await bre.network.provider['_init' as any]()
const node = bre.network.provider['_node' as any]

// Copy the options from the old VM instance and insert our new one.
// Copy the options from the old VM instance and create a new one.
const vm = node['_vm' as any]
const ovm = new VM({
...vm.opts,
stateManager: vm.stateManager,
emGasLimit: gasLimit,
const ovm = makeOVM({
evmOpts: {
...vm.opts,
stateManager: vm.stateManager,
},
ovmOpts: {
emGasLimit: ovmGasLimit,
},
})

// Initialize the OVM and replace the old VM.
await ovm.init()
node['_vm' as any] = ovm

// Hijack the gas estimation function.
node.estimateGas = async (txParams: any): Promise<{ estimation: any }> => {
node.estimateGas = async (): Promise<{ estimation: any }> => {
return {
estimation: new BN(gasLimit),
estimation: new BN(ovmGasLimit),
}
}

Expand Down
Loading