From 32b4d9883bf563c5601737113209e69a0f67fafb Mon Sep 17 00:00:00 2001 From: cbrazon Date: Tue, 1 Mar 2022 16:26:03 +0100 Subject: [PATCH 01/11] tests(common): complex genesis state tests added & documentation improved --- packages/common/package.json | 1 + packages/common/src/index.ts | 14 +++++++- packages/common/src/types.ts | 5 ++- packages/common/tests/customChains.spec.ts | 37 +++++++++++++++++++++- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/common/package.json b/packages/common/package.json index 948e5addbb..60a8af643d 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -33,6 +33,7 @@ "tape": "tape -r ts-node/register", "test": "npm run test:node && npm run test:browser", "test:node": "npm run tape -- ./tests/*.spec.ts", + "test:custom": "npm run tape -- ./tests/customChains.spec.ts", "test:browser": "karma start karma.conf.js", "docs:build": "typedoc --options typedoc.js" }, diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index cd186ca39f..cef964f6f3 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -141,13 +141,25 @@ export interface CommonOpts extends BaseOpts { * const common = new Common({ chain: 'myCustomChain1', customChains: [ myCustomChain1 ]}) * ``` * - * Pattern 2 (with genesis state, see {@link GenesisState} for format): + * Pattern 2 (with genesis state see {@link GenesisState} for format). Note that in {@link AccountState} there are two + * accepted types. This allows to easily insert accounts in the genesis state * * ```javascript * import myCustomChain1 from '[PATH_TO_MY_CHAINS]/myCustomChain1.json' * import chain1GenesisState from '[PATH_TO_GENESIS_STATES]/chain1GenesisState.json' * const common = new Common({ chain: 'myCustomChain1', customChains: [ [ myCustomChain1, chain1GenesisState ] ]}) * ``` + * + * A complex genesis state with Contract and EoA states would have the following format: + * + * ```javascript + * const complexState = { + * '0x0...01': '0x100', // For EoA + * '0x0...02': ['0x1', '0xBYTECODE', [[ keyOne, valueOne ], [ keyTwo, valueTwo ]]] // For contracts + * } + * ``` + * import myCustomChain1 from '[PATH_TO_MY_CHAINS]/myCustomChain1.json' + * const common = new Common({ chain: 'myCustomChain1', customChains: [ [ myCustomChain1, complexState ] ]}) */ customChains?: IChain[] | [IChain, GenesisState][] } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 99c659e20c..6a27253cbf 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -40,8 +40,11 @@ export interface Chain { } } +export type StoragePair = [key: string, value: string] +export type AccountState = [balance: string, code: string, storage: Array] // [balance, code, [[storageKey, storageValue]]] + export interface GenesisState { - [key: string]: string | [string, [[string, string]]] // balance | [balance, code, [[storageKey, storageValue]]] + [key: string]: string | AccountState } export interface eipsType { diff --git a/packages/common/tests/customChains.spec.ts b/packages/common/tests/customChains.spec.ts index a636a2c3df..aac9465031 100644 --- a/packages/common/tests/customChains.spec.ts +++ b/packages/common/tests/customChains.spec.ts @@ -5,7 +5,7 @@ import testnet from './data/testnet.json' import testnet2 from './data/testnet2.json' import testnet3 from './data/testnet3.json' -import { Chain as IChain, GenesisState } from '../src/types' +import { AccountState, Chain as IChain, GenesisState } from '../src/types' tape('[Common]: Custom chains', function (t: tape.Test) { t.test( @@ -208,6 +208,41 @@ tape('[Common]: Custom chains', function (t: tape.Test) { st.equal(c.hardforks()[3].forkHash, '0x215201ca', 'forkhash should be calculated correctly') + const contractAccount = '0x96fb4792cf2B3A7EF9842D1Af74f8c99C6F4fF63' + const eoaAccount = '0x0000000000000000000000000000000000000002' + const storage: Array<[string, string]> = [ + ['0x0000000000000000000000000000000000000001', '0x3'], + ['0x0000000000000000000000000000000000000002', '0x4'], + ] + + const contractState: AccountState = ['0x10000', '0xbca', storage] + const complexGenesisState = { + [contractAccount]: contractState, + [eoaAccount]: '0x100', + } + + c = new Common({ + chain: 'testnet', + customChains: [[testnet, complexGenesisState]], + }) + // Retrieve balance from EoA + st.deepEqual(c.genesisState()[eoaAccount], complexGenesisState[eoaAccount]) + + // Retrieve code of the contract account + st.deepEqual(c.genesisState()[contractAccount][1], complexGenesisState[contractAccount][1]) + + // Retrieve value of first stored space in storage of account (state of contract) + st.deepEqual( + c.genesisState()[contractAccount][2][0][1], + complexGenesisState[contractAccount][2][0][1] + ) + + // Retrieve value of second stored space in storage of account (state of contract) + st.deepEqual( + c.genesisState()[contractAccount][2][1][1], + complexGenesisState[contractAccount][2][1][1] + ) + st.end() }) }) From ce8c237d101aea1d45630d9d1ab8af77dd997eae Mon Sep 17 00:00:00 2001 From: cbrazon Date: Tue, 1 Mar 2022 17:10:17 +0100 Subject: [PATCH 02/11] chore(package.json): remove unused test script --- packages/common/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/common/package.json b/packages/common/package.json index 60a8af643d..948e5addbb 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -33,7 +33,6 @@ "tape": "tape -r ts-node/register", "test": "npm run test:node && npm run test:browser", "test:node": "npm run tape -- ./tests/*.spec.ts", - "test:custom": "npm run tape -- ./tests/customChains.spec.ts", "test:browser": "karma start karma.conf.js", "docs:build": "typedoc --options typedoc.js" }, From a832ce5aa51c0c7ef008f853cdb56012dbcd4ac7 Mon Sep 17 00:00:00 2001 From: cbrazon Date: Tue, 1 Mar 2022 21:52:02 +0100 Subject: [PATCH 03/11] chore(vm): trying to run transaction with custom genesis state --- packages/ethereum-tests | 2 +- packages/vm/package.json | 2 +- packages/vm/tests/api/customChain.spec.ts | 61 ++++++++++++++ .../api/testdata/testnetWithGenesisState.json | 79 +++++++++++++++++++ 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 packages/vm/tests/api/customChain.spec.ts create mode 100644 packages/vm/tests/api/testdata/testnetWithGenesisState.json diff --git a/packages/ethereum-tests b/packages/ethereum-tests index 6401889dec..005931a700 160000 --- a/packages/ethereum-tests +++ b/packages/ethereum-tests @@ -1 +1 @@ -Subproject commit 6401889dec4eee58e808fd178fb2c7f628a3e039 +Subproject commit 005931a700d4e4c4fd9e0d158771484403e7cb19 diff --git a/packages/vm/package.json b/packages/vm/package.json index ad98e35ac5..6d78fb8fba 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -37,7 +37,7 @@ "test:buildIntegrity": "npm run test:state -- --test='stackOverflow'", "test:blockchain": "npm run tester -- --blockchain", "test:blockchain:allForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin London ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5' | xargs -n1 | xargs -I v1 npm run tester -- --blockchain --fork=v1 --verify-test-amount-alltests", - "test:API": "npm run tape -- './tests/api/**/*.spec.ts'", + "test:customChain": "npm run tape -- './tests/api/**/customChain.spec.ts'", "test:API:browser": "karma start karma.conf.js", "test": "echo \"[INFO] Generic test cmd not used. See package.json for more specific test run cmds.\"", "lint": "../../config/cli/lint.sh", diff --git a/packages/vm/tests/api/customChain.spec.ts b/packages/vm/tests/api/customChain.spec.ts new file mode 100644 index 0000000000..4703e83879 --- /dev/null +++ b/packages/vm/tests/api/customChain.spec.ts @@ -0,0 +1,61 @@ +import tape from 'tape' +import Common from '@ethereumjs/common' +import testChain from './testdata/testnetWithGenesisState.json' +import VM from '../../src' +import { TransactionFactory } from '@ethereumjs/tx' +import { Block } from '@ethereumjs/block' + +const genesisState = { + ['0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b']: '0x6d6172697573766477000000', + ['0xbe862ad9abfe6f22bcb087716c7d89a26051f74c']: '0x6d6172697573766477000000', +} + +tape.skip('VM initialized with custom ', (t) => { + t.test('should transfer eth from already existant account', async (t) => { + const common = new Common({ chain: 'testnet', customChains: [[testChain, genesisState]] }) + // const vm = await VM.create({ common }) + const vm = new VM({ common }) + const privateKey = Buffer.from( + 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', + 'hex' + ) + + const unsignedTx = TransactionFactory.fromTxData( + { + type: 2, + to: '0x00000000000000000000000000000000000000ff', + value: '0x1', + gasLimit: 21_000, + maxFeePerGas: 7, + }, + { + common, + } + ) + const tx = unsignedTx.sign(privateKey) + + const block = Block.fromBlockData( + { + header: { + gasLimit: 21_000, + baseFeePerGas: 7, + }, + }, + { + common, + } + ) + await vm.runTx({ + tx, + block, + skipBalance: true, + }) + + // console.log({ result }) + + t.end() + + // const block = await vm.blockchain.getLatestBlock() + // console.log({ block }) + }) +}) diff --git a/packages/vm/tests/api/testdata/testnetWithGenesisState.json b/packages/vm/tests/api/testdata/testnetWithGenesisState.json new file mode 100644 index 0000000000..11cd9c4e87 --- /dev/null +++ b/packages/vm/tests/api/testdata/testnetWithGenesisState.json @@ -0,0 +1,79 @@ +{ + "name": "testnet", + "chainId": 110, + "networkId": 110, + "defaultHardfork": "london", + "consensus": { + "type": "pow", + "algorithm": "ethash" + }, + "comment": "Private test network with hash and state root defined for custom genesis state", + "url": "[TESTNET_URL]", + "genesis": { + "hash": "0x108c2843db2a0642ea6a8f20423ce4f28889b74a92760331fcc2507dd0aa28e9", + "timestamp": "0x0", + "gasLimit": 30000000, + "difficulty": 17179869184, + "nonce": "0x0000000000000042", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "stateRoot": "0x7bc54fdad88ac3edc66a9e4d8f09b0c16642b3730581558b8cca78e19a1ae1d2" + }, + "hardforks": [ + { + "name": "chainstart", + "block": 0 + }, + { + "name": "homestead", + "block": 0 + }, + { + "name": "tangerineWhistle", + "block": 0 + }, + { + "name": "spuriousDragon", + "block": 0 + }, + { + "name": "byzantium", + "block": 0 + }, + { + "name": "constantinople", + "block": 0 + }, + { + "name": "petersburg", + "block": 0 + }, + { + "name": "istanbul", + "block": 0 + }, + { + "name": "berlin", + "block": 0 + }, + { + "name": "london", + "block": 0 + } + ], + "bootstrapNodes": [ + { + "ip": "10.0.0.1", + "port": 30303, + "id": "11000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "location": "", + "comment": "" + }, + { + "ip": "10.0.0.2", + "port": 30303, + "id": "22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "location": "", + "comment": "" + } + ] +} From b7ee51b2d17bb325ea18de0e78fa6d1908e90d8d Mon Sep 17 00:00:00 2001 From: cbrzn Date: Wed, 2 Mar 2022 23:01:58 +0100 Subject: [PATCH 04/11] vm(genesis-state): activateGenesisState flag added to inject genesis state from common, tests on progress --- packages/common/src/types.ts | 13 ++- packages/common/tests/customChains.spec.ts | 5 +- packages/vm/src/index.ts | 38 +++++--- packages/vm/tests/api/customChain.spec.ts | 103 +++++++++++++++------ 4 files changed, 111 insertions(+), 48 deletions(-) diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 6a27253cbf..b96e4ccd83 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -1,4 +1,4 @@ -import { BN } from 'ethereumjs-util' +import { BN, PrefixedHexString } from 'ethereumjs-util' import { ConsensusAlgorithm, ConsensusType, Hardfork as HardforkName } from '.' export interface genesisStatesType { @@ -40,11 +40,16 @@ export interface Chain { } } -export type StoragePair = [key: string, value: string] -export type AccountState = [balance: string, code: string, storage: Array] // [balance, code, [[storageKey, storageValue]]] +type StoragePair = [key: PrefixedHexString, value: PrefixedHexString] + +export type AccountState = [ + balance: PrefixedHexString, + code: PrefixedHexString, + storage: Array +] export interface GenesisState { - [key: string]: string | AccountState + [key: PrefixedHexString]: PrefixedHexString | AccountState } export interface eipsType { diff --git a/packages/common/tests/customChains.spec.ts b/packages/common/tests/customChains.spec.ts index aac9465031..80ec19df26 100644 --- a/packages/common/tests/customChains.spec.ts +++ b/packages/common/tests/customChains.spec.ts @@ -211,8 +211,8 @@ tape('[Common]: Custom chains', function (t: tape.Test) { const contractAccount = '0x96fb4792cf2B3A7EF9842D1Af74f8c99C6F4fF63' const eoaAccount = '0x0000000000000000000000000000000000000002' const storage: Array<[string, string]> = [ - ['0x0000000000000000000000000000000000000001', '0x3'], - ['0x0000000000000000000000000000000000000002', '0x4'], + ['0x000000000000000000000000000001', '0x3'], + ['0x000000000000000000000000000002', '0x4'], ] const contractState: AccountState = ['0x10000', '0xbca', storage] @@ -225,6 +225,7 @@ tape('[Common]: Custom chains', function (t: tape.Test) { chain: 'testnet', customChains: [[testnet, complexGenesisState]], }) + // Retrieve balance from EoA st.deepEqual(c.genesisState()[eoaAccount], complexGenesisState[eoaAccount]) diff --git a/packages/vm/src/index.ts b/packages/vm/src/index.ts index 7b0ec7f428..b692118c63 100644 --- a/packages/vm/src/index.ts +++ b/packages/vm/src/index.ts @@ -93,6 +93,14 @@ export interface VMOpts { * Default: `false` */ activatePrecompiles?: boolean + /** + * If true, the state of the VM will add the genesis state given by {@link Common} to a new + * created state manager instance. Note that if stateManager option is also passed as argument + * this flag wont have any effect. + * + * Default: `undefined` + */ + activateGenesisState?: boolean /** * Allows unlimited contract sizes while debugging. By setting this to `true`, the check for * contract size limit of 24KB (see [EIP-170](https://git.io/vxZkK)) is bypassed. @@ -285,18 +293,24 @@ export default class VM extends AsyncEventEmitter { await this.blockchain.initPromise - if (this._opts.activatePrecompiles && !this._opts.stateManager) { - await this.stateManager.checkpoint() - // put 1 wei in each of the precompiles in order to make the accounts non-empty and thus not have them deduct `callNewAccount` gas. - await Promise.all( - Object.keys(precompiles) - .map((k: string): Address => new Address(Buffer.from(k, 'hex'))) - .map(async (address: Address) => { - const account = Account.fromAccountData({ balance: 1 }) - await this.stateManager.putAccount(address, account) - }) - ) - await this.stateManager.commit() + if (!this._opts.stateManager) { + if (this._opts.activateGenesisState) { + await this.stateManager.generateCanonicalGenesis() + } + + if (this._opts.activatePrecompiles) { + await this.stateManager.checkpoint() + // put 1 wei in each of the precompiles in order to make the accounts non-empty and thus not have them deduct `callNewAccount` gas. + await Promise.all( + Object.keys(precompiles) + .map((k: string): Address => new Address(Buffer.from(k, 'hex'))) + .map(async (address: Address) => { + const account = Account.fromAccountData({ balance: 1 }) + await this.stateManager.putAccount(address, account) + }) + ) + await this.stateManager.commit() + } } if (this._common.isActivatedEIP(2537)) { diff --git a/packages/vm/tests/api/customChain.spec.ts b/packages/vm/tests/api/customChain.spec.ts index 4703e83879..77cf00fb06 100644 --- a/packages/vm/tests/api/customChain.spec.ts +++ b/packages/vm/tests/api/customChain.spec.ts @@ -4,23 +4,64 @@ import testChain from './testdata/testnetWithGenesisState.json' import VM from '../../src' import { TransactionFactory } from '@ethereumjs/tx' import { Block } from '@ethereumjs/block' +import { AccountState } from '@ethereumjs/common/dist/types' +import { Interface } from '@ethersproject/abi' +import { Address } from 'ethereumjs-util' +const storage: Array<[string, string]> = [ + [ + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000000000000000000000000000004', + ], +] +const accountState: AccountState = [ + '0x0', + '0x6080604052600460005534801561001557600080fd5b5060b6806100246000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632e64cec114602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60008054905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea2646970667358221220338001095242a334ada78025237955fa36b6f2f895ea7f297b69af72f8bc7fd164736f6c63430008070033', + storage, +] + +/** + * The bytecode of this contract state represents: + * contract Storage { + * uint256 number = 4; + * function retrieve() public view returns (uint256){ + * return number; + * } + * } + */ + +const contractAddress = '0x61FfE691821291D02E9Ba5D33098ADcee71a3a17' const genesisState = { - ['0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b']: '0x6d6172697573766477000000', - ['0xbe862ad9abfe6f22bcb087716c7d89a26051f74c']: '0x6d6172697573766477000000', + '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b': '0x6d6172697573766477000000', + '0xbe862ad9abfe6f22bcb087716c7d89a26051f74c': '0x6d6172697573766477000000', + [contractAddress]: accountState, } -tape.skip('VM initialized with custom ', (t) => { - t.test('should transfer eth from already existant account', async (t) => { - const common = new Common({ chain: 'testnet', customChains: [[testChain, genesisState]] }) - // const vm = await VM.create({ common }) - const vm = new VM({ common }) - const privateKey = Buffer.from( - 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', - 'hex' - ) +const common = new Common({ + chain: 'testnet', + customChains: [[testChain, genesisState]], +}) +const block = Block.fromBlockData( + { + header: { + gasLimit: 21_000, + baseFeePerGas: 7, + }, + }, + { + common, + } +) +const privateKey = Buffer.from( + 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', + 'hex' +) + +tape('VM initialized with custom state ', (t) => { + t.test('should transfer eth from already existent account', async (t) => { + const vm = await VM.create({ common, activateGenesisState: true }) - const unsignedTx = TransactionFactory.fromTxData( + const unsignedTransferTx = TransactionFactory.fromTxData( { type: 2, to: '0x00000000000000000000000000000000000000ff', @@ -32,30 +73,32 @@ tape.skip('VM initialized with custom ', (t) => { common, } ) - const tx = unsignedTx.sign(privateKey) + const tx = unsignedTransferTx.sign(privateKey) - const block = Block.fromBlockData( - { - header: { - gasLimit: 21_000, - baseFeePerGas: 7, - }, - }, - { - common, - } - ) - await vm.runTx({ + const result = await vm.runTx({ tx, block, - skipBalance: true, }) - // console.log({ result }) + t.equal(result.gasUsed.toString(), '21000') + }) + t.test('should retrieve value from storage', async (t) => { + const vm = await VM.create({ common, activateGenesisState: true }) + const sigHash = new Interface(['function retrieve()']).getSighash('retrieve') - t.end() + const callResult = await vm.runCall({ + to: Address.fromString(contractAddress), + data: Buffer.from(sigHash.slice(2), 'hex'), + caller: Address.fromPrivateKey(privateKey), + }) + + // Returned value should be 4, because we are trying to trigger the method `retrieve` + // in the contract, which returns the variable stored in slot 0x00..01 + t.equal( + callResult.execResult.returnValue.toString('hex'), + genesisState[contractAddress][2][0][1].slice(2) + ) - // const block = await vm.blockchain.getLatestBlock() - // console.log({ block }) + t.end() }) }) From 028f54e6e5156def98b359d77632fbadf61fee30 Mon Sep 17 00:00:00 2001 From: cbrzn Date: Fri, 4 Mar 2022 15:55:54 +0100 Subject: [PATCH 05/11] vm(genesis-state): tests finished --- packages/vm/tests/api/customChain.spec.ts | 57 ++++++++++--- packages/vm/tests/api/index.spec.ts | 28 ------- packages/vm/tests/api/testdata/testnet.json | 4 + .../api/testdata/testnetWithGenesisState.json | 79 ------------------- 4 files changed, 48 insertions(+), 120 deletions(-) delete mode 100644 packages/vm/tests/api/testdata/testnetWithGenesisState.json diff --git a/packages/vm/tests/api/customChain.spec.ts b/packages/vm/tests/api/customChain.spec.ts index 77cf00fb06..04f272f977 100644 --- a/packages/vm/tests/api/customChain.spec.ts +++ b/packages/vm/tests/api/customChain.spec.ts @@ -1,22 +1,23 @@ import tape from 'tape' -import Common from '@ethereumjs/common' -import testChain from './testdata/testnetWithGenesisState.json' +import Common, { Hardfork } from '@ethereumjs/common' +import testChain from './testdata/testnet.json' import VM from '../../src' import { TransactionFactory } from '@ethereumjs/tx' import { Block } from '@ethereumjs/block' import { AccountState } from '@ethereumjs/common/dist/types' import { Interface } from '@ethersproject/abi' import { Address } from 'ethereumjs-util' +import testnetMerge from './testdata/testnetMerge.json' const storage: Array<[string, string]> = [ [ - '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000004', ], ] const accountState: AccountState = [ '0x0', - '0x6080604052600460005534801561001557600080fd5b5060b6806100246000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632e64cec114602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60008054905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea2646970667358221220338001095242a334ada78025237955fa36b6f2f895ea7f297b69af72f8bc7fd164736f6c63430008070033', + '0x6080604052348015600f57600080fd5b506004361060285760003560e01c80632e64cec114602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60008054905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea2646970667358221220338001095242a334ada78025237955fa36b6f2f895ea7f297b69af72f8bc7fd164736f6c63430008070033', storage, ] @@ -30,7 +31,7 @@ const accountState: AccountState = [ * } */ -const contractAddress = '0x61FfE691821291D02E9Ba5D33098ADcee71a3a17' +const contractAddress = '0x3651539F2E119a27c606cF0cB615410eCDaAE62a' const genesisState = { '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b': '0x6d6172697573766477000000', '0xbe862ad9abfe6f22bcb087716c7d89a26051f74c': '0x6d6172697573766477000000', @@ -39,6 +40,7 @@ const genesisState = { const common = new Common({ chain: 'testnet', + hardfork: 'london', customChains: [[testChain, genesisState]], }) const block = Block.fromBlockData( @@ -61,10 +63,11 @@ tape('VM initialized with custom state ', (t) => { t.test('should transfer eth from already existent account', async (t) => { const vm = await VM.create({ common, activateGenesisState: true }) + const to = '0x00000000000000000000000000000000000000ff' const unsignedTransferTx = TransactionFactory.fromTxData( { type: 2, - to: '0x00000000000000000000000000000000000000ff', + to, value: '0x1', gasLimit: 21_000, maxFeePerGas: 7, @@ -74,14 +77,18 @@ tape('VM initialized with custom state ', (t) => { } ) const tx = unsignedTransferTx.sign(privateKey) - const result = await vm.runTx({ tx, block, }) + const toAddress = Address.fromString(to) + const receiverAddress = await vm.stateManager.getAccount(toAddress) t.equal(result.gasUsed.toString(), '21000') + t.equal(receiverAddress.balance.toString(), '1') + t.end() }) + t.test('should retrieve value from storage', async (t) => { const vm = await VM.create({ common, activateGenesisState: true }) const sigHash = new Interface(['function retrieve()']).getSighash('retrieve') @@ -92,13 +99,37 @@ tape('VM initialized with custom state ', (t) => { caller: Address.fromPrivateKey(privateKey), }) + const [, , storage] = genesisState[contractAddress] // Returned value should be 4, because we are trying to trigger the method `retrieve` - // in the contract, which returns the variable stored in slot 0x00..01 - t.equal( - callResult.execResult.returnValue.toString('hex'), - genesisState[contractAddress][2][0][1].slice(2) - ) - + // in the contract, which returns the variable stored in slot 0x00..00 + t.equal(callResult.execResult.returnValue.toString('hex'), storage[0][1].slice(2)) t.end() }) + + t.test('hardforkByBlockNumber, hardforkByTD', async (st) => { + const customChains = [testnetMerge] + const common = new Common({ chain: 'testnetMerge', hardfork: Hardfork.Istanbul, customChains }) + + let vm = await VM.create({ common, hardforkByBlockNumber: true }) + st.equal((vm as any)._hardforkByBlockNumber, true, 'should set hardforkByBlockNumber option') + + vm = await VM.create({ common, hardforkByTD: 5001 }) + st.equal((vm as any)._hardforkByTD, 5001, 'should set hardforkByTD option') + + try { + await VM.create({ common, hardforkByBlockNumber: true, hardforkByTD: 3000 }) + st.fail('should not reach this') + } catch (e: any) { + const msg = + 'should throw if hardforkByBlockNumber and hardforkByTD options are used in conjunction' + st.ok( + e.message.includes( + `The hardforkByBlockNumber and hardforkByTD options can't be used in conjunction` + ), + msg + ) + } + + st.end() + }) }) diff --git a/packages/vm/tests/api/index.spec.ts b/packages/vm/tests/api/index.spec.ts index 82252c52bc..c8fa451919 100644 --- a/packages/vm/tests/api/index.spec.ts +++ b/packages/vm/tests/api/index.spec.ts @@ -8,7 +8,6 @@ import { isRunningInKarma } from '../util' import { setupVM } from './utils' import testnet from './testdata/testnet.json' import testnet2 from './testdata/testnet2.json' -import testnetMerge from './testdata/testnetMerge.json' // explicitly import util and buffer, // needed for karma-typescript bundling @@ -131,33 +130,6 @@ tape('VM -> common (chain, HFs, EIPs)', (t) => { }) tape('VM -> hardforkByBlockNumber, hardforkByTD, state (deprecated), blockchain', (t) => { - t.test('hardforkByBlockNumber, hardforkByTD', async (st) => { - const customChains = [testnetMerge] - const common = new Common({ chain: 'testnetMerge', hardfork: Hardfork.Istanbul, customChains }) - - let vm = await VM.create({ common, hardforkByBlockNumber: true }) - st.equal((vm as any)._hardforkByBlockNumber, true, 'should set hardforkByBlockNumber option') - - vm = await VM.create({ common, hardforkByTD: 5001 }) - st.equal((vm as any)._hardforkByTD, 5001, 'should set hardforkByTD option') - - try { - await VM.create({ common, hardforkByBlockNumber: true, hardforkByTD: 3000 }) - st.fail('should not reach this') - } catch (e: any) { - const msg = - 'should throw if hardforkByBlockNumber and hardforkByTD options are used in conjunction' - st.ok( - e.message.includes( - `The hardforkByBlockNumber and hardforkByTD options can't be used in conjunction` - ), - msg - ) - } - - st.end() - }) - t.test('should work with trie (state) provided', async (st) => { const trie = new Trie() const vm = new VM({ state: trie, activatePrecompiles: true }) diff --git a/packages/vm/tests/api/testdata/testnet.json b/packages/vm/tests/api/testdata/testnet.json index af756b3f3a..03cf4b6125 100644 --- a/packages/vm/tests/api/testdata/testnet.json +++ b/packages/vm/tests/api/testdata/testnet.json @@ -54,6 +54,10 @@ { "name": "berlin", "block": 8 + }, + { + "name": "london", + "block": 9 } ], "bootstrapNodes": [ diff --git a/packages/vm/tests/api/testdata/testnetWithGenesisState.json b/packages/vm/tests/api/testdata/testnetWithGenesisState.json deleted file mode 100644 index 11cd9c4e87..0000000000 --- a/packages/vm/tests/api/testdata/testnetWithGenesisState.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "testnet", - "chainId": 110, - "networkId": 110, - "defaultHardfork": "london", - "consensus": { - "type": "pow", - "algorithm": "ethash" - }, - "comment": "Private test network with hash and state root defined for custom genesis state", - "url": "[TESTNET_URL]", - "genesis": { - "hash": "0x108c2843db2a0642ea6a8f20423ce4f28889b74a92760331fcc2507dd0aa28e9", - "timestamp": "0x0", - "gasLimit": 30000000, - "difficulty": 17179869184, - "nonce": "0x0000000000000042", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "stateRoot": "0x7bc54fdad88ac3edc66a9e4d8f09b0c16642b3730581558b8cca78e19a1ae1d2" - }, - "hardforks": [ - { - "name": "chainstart", - "block": 0 - }, - { - "name": "homestead", - "block": 0 - }, - { - "name": "tangerineWhistle", - "block": 0 - }, - { - "name": "spuriousDragon", - "block": 0 - }, - { - "name": "byzantium", - "block": 0 - }, - { - "name": "constantinople", - "block": 0 - }, - { - "name": "petersburg", - "block": 0 - }, - { - "name": "istanbul", - "block": 0 - }, - { - "name": "berlin", - "block": 0 - }, - { - "name": "london", - "block": 0 - } - ], - "bootstrapNodes": [ - { - "ip": "10.0.0.1", - "port": 30303, - "id": "11000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "location": "", - "comment": "" - }, - { - "ip": "10.0.0.2", - "port": 30303, - "id": "22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "location": "", - "comment": "" - } - ] -} From 556a809ee6dff94fb40f52eb82ebb1f79869aa6b Mon Sep 17 00:00:00 2001 From: cbrzn Date: Fri, 4 Mar 2022 16:06:46 +0100 Subject: [PATCH 06/11] vm: remove non used script --- packages/vm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vm/package.json b/packages/vm/package.json index 6d78fb8fba..ad98e35ac5 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -37,7 +37,7 @@ "test:buildIntegrity": "npm run test:state -- --test='stackOverflow'", "test:blockchain": "npm run tester -- --blockchain", "test:blockchain:allForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin London ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5' | xargs -n1 | xargs -I v1 npm run tester -- --blockchain --fork=v1 --verify-test-amount-alltests", - "test:customChain": "npm run tape -- './tests/api/**/customChain.spec.ts'", + "test:API": "npm run tape -- './tests/api/**/*.spec.ts'", "test:API:browser": "karma start karma.conf.js", "test": "echo \"[INFO] Generic test cmd not used. See package.json for more specific test run cmds.\"", "lint": "../../config/cli/lint.sh", From 399408e69a8f5f47fac14c1213c6429b26eb46d9 Mon Sep 17 00:00:00 2001 From: cbrzn Date: Fri, 4 Mar 2022 18:06:58 +0100 Subject: [PATCH 07/11] vm(genesis-state): add documentation in readme about activate genesis state flag --- packages/vm/README.md | 44 ++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/vm/README.md b/packages/vm/README.md index 14f1c3afcd..c2b8431ae0 100644 --- a/packages/vm/README.md +++ b/packages/vm/README.md @@ -7,7 +7,7 @@ [![Discord][discord-badge]][discord-link] | TypeScript implementation of the Ethereum VM. | -| --- | +| --------------------------------------------- | Note: this `README` reflects the state of the library from `v5.0.0` onwards. See `README` from the [standalone repository](https://github.com/ethereumjs/ethereumjs-vm) for an introduction on the last preceding release. @@ -104,7 +104,7 @@ const hardforkByBlockNumber = true const vm = new VM({ common, hardforkByBlockNumber }) const serialized = Buffer.from('f901f7a06bfee7294bf4457...', 'hex') -const block = Block.fromRLPSerializedBlock(serialized, { hardforkByBlockNumber }) +const block = Block.fromRLPSerializedBlock(serialized, { hardforkByBlockNumber }) const result = await vm.runBlock(block) ``` @@ -140,6 +140,24 @@ const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Berlin }) const vm = new VM({ common }) ``` +## Custom genesis state support + +If you want to create a new instance of the VM and add your own genesis state, you can do it by passing a `Common` +instance with [custom genesis state]("../common/README.md#initialize-using-customchains-array) and passing the flag `activateGenesisState` in `VMOpts`, e.g.: + +```typescript +import Common from '@ethereumjs/common' +import VM from '@ethereumjs/vm' +import myCustomChain1 from '[PATH_TO_MY_CHAINS]/myCustomChain1.json' +import chain1GenesisState from '[PATH_TO_GENESIS_STATES]/chain1GenesisState.json' + +const common = new Common({ + chain: 'myCustomChain1', + customChains: [[myCustomChain1, chain1GenesisState]], +}) +const vm = new VM({ common, activateGenesisState: true }) +``` + ## EIP Support It is possible to individually activate EIP support in the VM by instantiate the `Common` instance passed @@ -218,17 +236,17 @@ If you want to understand your VM runs we have added a hierarchically structured The following loggers are currently available: -| Logger | Description | -| - | - | -| `vm:block` | Block operations (run txs, generating receipts, block rewards,...) | -| `vm:tx` | Transaction operations (account updates, checkpointing,...) | -| `vm:tx:gas` | Transaction gas logger | -| `vm:evm` | EVM control flow, CALL or CREATE message execution | -| `vm:evm:gas` | EVM gas logger | -| `vm:eei:gas` | EEI gas logger | -| `vm:state`| StateManager logger | -| `vm:ops` | Opcode traces | -| `vm:ops:[Lower-case opcode name]` | Traces on a specific opcode | +| Logger | Description | +| --------------------------------- | ------------------------------------------------------------------ | +| `vm:block` | Block operations (run txs, generating receipts, block rewards,...) | +| `vm:tx` |  Transaction operations (account updates, checkpointing,...)  | +| `vm:tx:gas` |  Transaction gas logger | +| `vm:evm` |  EVM control flow, CALL or CREATE message execution | +| `vm:evm:gas` |  EVM gas logger | +| `vm:eei:gas` |  EEI gas logger | +| `vm:state` | StateManager logger | +| `vm:ops` |  Opcode traces | +| `vm:ops:[Lower-case opcode name]` | Traces on a specific opcode | Here are some examples for useful logger combinations. From 566377e145540aedb21fa8e4726865b1dd33c893 Mon Sep 17 00:00:00 2001 From: cbrzn Date: Fri, 4 Mar 2022 18:10:40 +0100 Subject: [PATCH 08/11] vm: fix link on documentation --- packages/vm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vm/README.md b/packages/vm/README.md index c2b8431ae0..ece7012a49 100644 --- a/packages/vm/README.md +++ b/packages/vm/README.md @@ -143,7 +143,7 @@ const vm = new VM({ common }) ## Custom genesis state support If you want to create a new instance of the VM and add your own genesis state, you can do it by passing a `Common` -instance with [custom genesis state]("../common/README.md#initialize-using-customchains-array) and passing the flag `activateGenesisState` in `VMOpts`, e.g.: +instance with [custom genesis state](../common/README.md#initialize-using-customchains-array) and passing the flag `activateGenesisState` in `VMOpts`, e.g.: ```typescript import Common from '@ethereumjs/common' From 97e89c41e82ac249a1b5f650433450cb47de17fd Mon Sep 17 00:00:00 2001 From: Cesar Brazon Date: Mon, 7 Mar 2022 00:06:32 +0100 Subject: [PATCH 09/11] Update packages/vm/src/index.ts Co-authored-by: Ryan Ghods --- packages/vm/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vm/src/index.ts b/packages/vm/src/index.ts index b692118c63..6756e92e28 100644 --- a/packages/vm/src/index.ts +++ b/packages/vm/src/index.ts @@ -96,9 +96,9 @@ export interface VMOpts { /** * If true, the state of the VM will add the genesis state given by {@link Common} to a new * created state manager instance. Note that if stateManager option is also passed as argument - * this flag wont have any effect. + * this flag won't have any effect. * - * Default: `undefined` + * Default: `false` */ activateGenesisState?: boolean /** From fb2c9a4d880d41e09f121165d7ced88408e3a9fe Mon Sep 17 00:00:00 2001 From: cbrzn Date: Mon, 7 Mar 2022 00:58:43 +0100 Subject: [PATCH 10/11] remove change in ethereum test sub module --- packages/ethereum-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-tests b/packages/ethereum-tests index 005931a700..6401889dec 160000 --- a/packages/ethereum-tests +++ b/packages/ethereum-tests @@ -1 +1 @@ -Subproject commit 005931a700d4e4c4fd9e0d158771484403e7cb19 +Subproject commit 6401889dec4eee58e808fd178fb2c7f628a3e039 From 27f37ba402aca8137c63e562f15bcb787a8d0e78 Mon Sep 17 00:00:00 2001 From: cbrzn Date: Mon, 7 Mar 2022 01:09:59 +0100 Subject: [PATCH 11/11] common: documentation of genesis state init improved --- packages/common/src/index.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index cef964f6f3..d732ffd5f0 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -141,25 +141,31 @@ export interface CommonOpts extends BaseOpts { * const common = new Common({ chain: 'myCustomChain1', customChains: [ myCustomChain1 ]}) * ``` * - * Pattern 2 (with genesis state see {@link GenesisState} for format). Note that in {@link AccountState} there are two - * accepted types. This allows to easily insert accounts in the genesis state + * Pattern 2 (with genesis state see {@link GenesisState} for format): * * ```javascript + * const simpleState = { + * '0x0...01': '0x100', // For EoA + * } * import myCustomChain1 from '[PATH_TO_MY_CHAINS]/myCustomChain1.json' * import chain1GenesisState from '[PATH_TO_GENESIS_STATES]/chain1GenesisState.json' - * const common = new Common({ chain: 'myCustomChain1', customChains: [ [ myCustomChain1, chain1GenesisState ] ]}) + * const common = new Common({ chain: 'myCustomChain1', customChains: [ [ myCustomChain1, simpleState ] ]}) * ``` * + * Pattern 3 (with complex genesis state, containing contract accounts and storage). + * Note that in {@link AccountState} there are two + * accepted types. This allows to easily insert accounts in the genesis state: + * * A complex genesis state with Contract and EoA states would have the following format: * * ```javascript * const complexState = { * '0x0...01': '0x100', // For EoA - * '0x0...02': ['0x1', '0xBYTECODE', [[ keyOne, valueOne ], [ keyTwo, valueTwo ]]] // For contracts + * '0x0...02': ['0x1', '0xRUNTIME_BYTECODE', [[ keyOne, valueOne ], [ keyTwo, valueTwo ]]] // For contracts * } - * ``` * import myCustomChain1 from '[PATH_TO_MY_CHAINS]/myCustomChain1.json' * const common = new Common({ chain: 'myCustomChain1', customChains: [ [ myCustomChain1, complexState ] ]}) + * ``` */ customChains?: IChain[] | [IChain, GenesisState][] }