-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Add state dump utilities to the contract package #287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cd8ca79
44cca13
d625d28
0b72117
736f6eb
4890b07
63b1b84
db671e5
674813a
0762925
d0805b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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)) | ||
| })() |
| 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 | ||
| 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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: indentation off
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh rip, I think this is just |
||
| }, | ||
| ], | ||
| }) | ||
|
|
||
| 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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this option was removed from being useful since
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`)) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export * from './deployment' | ||
| export * from './contract-imports' | ||
| export * from './dump' |
There was a problem hiding this comment.
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
codeHashas well, but it could be derived from the contract code. Removes the dependency of the consumer needing to have akeccakimplementation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Will do.