diff --git a/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts b/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts index c17fc1e130..fdc229ccd5 100644 --- a/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts +++ b/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts @@ -1,5 +1,6 @@ import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' +import { callbackCheckVerifyCallReturnValue } from '../types/index' class VerifyCallReturnValue extends EventEmitter { command (this: NightwatchBrowser, address: string, checks: string[]): NightwatchBrowser { @@ -13,7 +14,7 @@ class VerifyCallReturnValue extends EventEmitter { } } -function verifyCallReturnValue (browser: NightwatchBrowser, address: string, checks: string[], done: VoidFunction) { +function verifyCallReturnValue (browser: NightwatchBrowser, address: string, checks: string[] | callbackCheckVerifyCallReturnValue, done: VoidFunction) { browser.execute(function (address: string) { const nodes = document.querySelectorAll('#instance' + address + ' [data-id="udapp_value"]') as NodeListOf const ret = [] @@ -23,8 +24,13 @@ function verifyCallReturnValue (browser: NightwatchBrowser, address: string, che } return ret }, [address], function (result) { - for (const k in checks) { - browser.assert.equal(result.value[k].trim(), checks[k].trim()) + if (typeof checks === 'function') { + const ret = checks(result.value as string[]) + if (!ret.pass) browser.assert.fail(ret.message) + } else { + for (const k in checks) { + browser.assert.equal(result.value[k].trim(), checks[k].trim()) + } } done() }) diff --git a/apps/remix-ide-e2e/src/tests/blockchain.test.ts b/apps/remix-ide-e2e/src/tests/blockchain.test.ts new file mode 100644 index 0000000000..c44463ee30 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/blockchain.test.ts @@ -0,0 +1,59 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + '@disabled': true, + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done) + }, + '@sources': function () { + return '' + }, + + 'Execute a call that retrieve previous block hashes #group1': function (browser: NightwatchBrowser) { + const code = ` + contract A { + function foo(uint p) public view returns(bytes32) { + return blockhash(p); + } + }` + browser.testContracts('test.sol',{ content: code } , ['A']) + .clickLaunchIcon('udapp') + .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite + .click('.udapp_contractActionsContainerSingle > button') + .clickInstance(0) + .clickFunction('foo - call', { types: 'uint256 p', values: '0' }) + .perform((done) => { + browser.getAddressAtPosition(0, (address) => { + browser + .verifyCallReturnValue(address, (values: string[]) => { + // should be looking like: ['0:bytes32: 0x0391a96b0b74805e5fbb79a18840548c5b6c0f1c5e933fc5e3ee015823856e00'] + const value = values[0].replace('0:bytes32: ', '') + let pass = value !== '0x0000000000000000000000000000000000000000000000000000000000000000' && value !== '0x' + return { + pass, + message: pass ? 'pass' : 'a non empty blockhash should be returned' + } + }) + .perform(() => done()) + }) + }) + .clickFunction('foo - call', { types: 'uint256 p', values: '1' }) + .perform((done) => { + browser.getAddressAtPosition(0, (address) => { + browser + .verifyCallReturnValue(address, (values: string[]) => { + // should be looking like: ['0:bytes32: 0x0391a96b0b74805e5fbb79a18840548c5b6c0f1c5e933fc5e3ee015823856e00'] + const value = values[0].replace('0:bytes32: ', '') + let pass = value !== '0x0000000000000000000000000000000000000000000000000000000000000000' && value !== '0x' + return { + pass, + message: pass ? 'pass' : 'a non empty blockhash should be returned' + } + }) + .perform(() => done()) + }) + }).end() + } +} diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index 49b9b07607..eacb27c9ee 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -1,6 +1,7 @@ // Merge custom command types with nightwatch types /* eslint-disable no-use-before-define */ import { NightwatchBrowser } from 'nightwatch' // eslint-disable-line @typescript-eslint/no-unused-vars +export type callbackCheckVerifyCallReturnValue = (values: string[]) => { message: string, pass: boolean } declare module 'nightwatch' { export interface NightwatchCustomCommands { @@ -41,7 +42,7 @@ declare module 'nightwatch' { testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser, getEditorValue(callback: (content: string) => void): NightwatchBrowser, getInstalledPlugins(cb: (plugins: string[]) => void): NightwatchBrowser, - verifyCallReturnValue(address: string, checks: string[]): NightwatchBrowser, + verifyCallReturnValue(address: string, checks: string[] | callbackCheckVerifyCallReturnValue): NightwatchBrowser, testEditorValue(testvalue: string): NightwatchBrowser, removeFile(path: string, workspace: string): NightwatchBrowser, switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult) => void): NightwatchBrowser, diff --git a/apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx b/apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx index ff0f305568..a2ae20923c 100644 --- a/apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx +++ b/apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx @@ -14,7 +14,7 @@ export class MainnetForkVMProvider extends BasicVMProvider { version: packageJson.version }, blockchain) this.blockchain = blockchain - this.fork = 'london' + this.fork = 'merge' this.nodeUrl = 'https://mainnet.infura.io/v3/08b2a484451e4635a28b3d8234f24332' this.blockNumber = 'latest' } diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index 0aec55082c..bd9817269e 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -82,7 +82,7 @@ export class Blockchain extends Plugin { return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false } }, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit()) - this.txRunner = new TxRunner(web3Runner, { runAsync: true }) + this.txRunner = new TxRunner(web3Runner, {}) this.networkcallid = 0 this.networkStatus = { network: { name: ' - ', id: ' - ' } } @@ -598,7 +598,7 @@ export class Blockchain extends Plugin { } }) }) - this.txRunner = new TxRunner(web3Runner, { runAsync: true }) + this.txRunner = new TxRunner(web3Runner, {}) } /** diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index 3f7ecf6b6b..c362b9f746 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -24,7 +24,7 @@ export class ExecutionContext { this.lastBlock = null this.blockGasLimitDefault = 4300000 this.blockGasLimit = this.blockGasLimitDefault - this.currentFork = 'london' + this.currentFork = 'merge' this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3' this.customNetWorks = {} this.blocks = {} @@ -161,7 +161,7 @@ export class ExecutionContext { try { this.currentFork = execution.forkAt(await web3.eth.net.getId(), block.number) } catch (e) { - this.currentFork = 'london' + this.currentFork = 'merge' console.log(`unable to detect fork, defaulting to ${this.currentFork}..`) console.error(e) } diff --git a/libs/remix-debug/src/code/codeUtils.ts b/libs/remix-debug/src/code/codeUtils.ts index 6590cfc01d..f9784bb135 100644 --- a/libs/remix-debug/src/code/codeUtils.ts +++ b/libs/remix-debug/src/code/codeUtils.ts @@ -46,7 +46,7 @@ type Opcode = { * information about the opcode. */ export function parseCode (raw) { - const common = new Common({ chain: 'mainnet', hardfork: 'london' }) + const common = new Common({ chain: 'mainnet', hardfork: 'merge' }) const opcodes = getOpcodesForHF(common).opcodes const code = [] diff --git a/libs/remix-debug/src/trace/traceManager.ts b/libs/remix-debug/src/trace/traceManager.ts index ed75691530..e15f8e180b 100644 --- a/libs/remix-debug/src/trace/traceManager.ts +++ b/libs/remix-debug/src/trace/traceManager.ts @@ -40,7 +40,7 @@ export class TraceManager { const networkId = await this.web3.eth.net.getId() this.fork = execution.forkAt(networkId, tx.blockNumber) } catch (e) { - this.fork = 'london' + this.fork = 'merge' console.log(`unable to detect fork, defaulting to ${this.fork}..`) console.error(e) } diff --git a/libs/remix-lib/src/execution/forkAt.ts b/libs/remix-lib/src/execution/forkAt.ts index 8e45ce9acf..fe7b1b0804 100644 --- a/libs/remix-lib/src/execution/forkAt.ts +++ b/libs/remix-lib/src/execution/forkAt.ts @@ -58,6 +58,10 @@ const forks = { { number: 15050000, name: 'grayGlacier' + }, + { + number: 15537394, + name: 'merge' } ], 3: [ diff --git a/libs/remix-lib/src/execution/txRunner.ts b/libs/remix-lib/src/execution/txRunner.ts index 544b9ef1a9..47f7458ec9 100644 --- a/libs/remix-lib/src/execution/txRunner.ts +++ b/libs/remix-lib/src/execution/txRunner.ts @@ -14,7 +14,6 @@ export type Transaction = { export class TxRunner { event - runAsync pendingTxs queusTxs opt @@ -24,8 +23,6 @@ export class TxRunner { this.internalRunner = internalRunner this.event = new EventManager() - this.runAsync = this.opt.runAsync || true // We have to run like this cause the VM Event Manager does not support running multiple txs at the same time. - this.pendingTxs = {} this.queusTxs = [] } @@ -44,7 +41,7 @@ export class TxRunner { } function run (self, tx: Transaction, stamp, confirmationCb, gasEstimationForceSend = null, promptCb = null, callback = null) { - if (!self.runAsync && Object.keys(self.pendingTxs).length) { + if (Object.keys(self.pendingTxs).length) { return self.queusTxs.push({ tx, stamp, callback }) } self.pendingTxs[stamp] = tx diff --git a/libs/remix-lib/src/execution/txRunnerVM.ts b/libs/remix-lib/src/execution/txRunnerVM.ts index ed3562f30e..5a800e2326 100644 --- a/libs/remix-lib/src/execution/txRunnerVM.ts +++ b/libs/remix-lib/src/execution/txRunnerVM.ts @@ -21,13 +21,13 @@ export type VMExecutionCallBack = (error: string | Error, result?: VMexecutionRe export class TxRunnerVM { event blockNumber - runAsync pendingTxs vmaccounts queusTxs blocks logsManager commonContext + blockParentHash nextNonceForCall: number getVMObject: () => any @@ -38,9 +38,6 @@ export class TxRunnerVM { this.getVMObject = getVMObject this.commonContext = this.getVMObject().common this.blockNumber = 0 - this.runAsync = true - this.blockNumber = 0 // The VM is running in Homestead mode, which started at this block. - this.runAsync = false // We have to run like this cause the VM Event Manager does not support running multiple txs at the same time. this.pendingTxs = {} this.vmaccounts = vmaccounts this.queusTxs = [] @@ -52,6 +49,9 @@ export class TxRunnerVM { For this to function we also need to skip nonce validation, in the vm: `{ skipNonce: true }` */ this.nextNonceForCall = 0 + + const vm = this.getVMObject().vm + this.blockParentHash = vm.blockchain.genesisBlock.hash() } execute (args: InternalTransaction, confirmationCb, gasEstimationForceSend, promptCb, callback: VMExecutionCallBack) { @@ -68,12 +68,11 @@ export class TxRunnerVM { } runInVm (from: string, to: string, data: string, value: string, gasLimit: number, useCall: boolean, callback: VMExecutionCallBack) { - const self = this let account - if (!from && useCall && Object.keys(self.vmaccounts).length) { - from = Object.keys(self.vmaccounts)[0] - account = self.vmaccounts[from] - } else account = self.vmaccounts[from] + if (!from && useCall && Object.keys(this.vmaccounts).length) { + from = Object.keys(this.vmaccounts)[0] + account = this.vmaccounts[from] + } else account = this.vmaccounts[from] if (!account) { return callback('Invalid account selected') @@ -106,23 +105,29 @@ export class TxRunnerVM { const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e'] const difficulties = [69762765929000, 70762765929000, 71762765929000] - const difficulty = this.commonContext.consensusType() === ConsensusType.ProofOfStake ? 0 : difficulties[self.blockNumber % difficulties.length] + const difficulty = this.commonContext.consensusType() === ConsensusType.ProofOfStake ? 0 : difficulties[this.blockNumber % difficulties.length] + const blocknumber = this.blockNumber + 1 const block = Block.fromBlockData({ header: { timestamp: new Date().getTime() / 1000 | 0, - number: self.blockNumber, - coinbase: coinbases[self.blockNumber % coinbases.length], + number: blocknumber, + coinbase: coinbases[blocknumber % coinbases.length], difficulty, gasLimit, - baseFeePerGas: EIP1559 ? '0x1' : undefined + baseFeePerGas: EIP1559 ? '0x1' : undefined, + parentHash: this.blockParentHash }, transactions: [tx] - }, { common: this.commonContext }) + }, { common: this.commonContext, hardforkByBlockNumber: false, hardforkByTTD: undefined }) if (!useCall) { - ++self.blockNumber - this.runBlockInVm(tx, block, callback) + this.blockNumber = this.blockNumber + 1 + this.blockParentHash = block.hash() + this.runBlockInVm(tx, block, (err, result) => { + if (!err) this.getVMObject().vm.blockchain.putBlock(block) + callback(err, result) + }) } else { this.getVMObject().stateManager.checkpoint().then(() => { this.runBlockInVm(tx, block, (err, result) => { diff --git a/libs/remix-simulator/src/genesis.ts b/libs/remix-simulator/src/genesis.ts deleted file mode 100644 index a17ea904c2..0000000000 --- a/libs/remix-simulator/src/genesis.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Block } from '@ethereumjs/block' -import { ConsensusType } from '@ethereumjs/common' - -export function generateBlock (vmContext) { - const common = vmContext.vmObject().common - - const difficulty = common.consensusType() === ConsensusType.ProofOfStake ? 0 : 69762765929000 - - return new Promise((resolve, reject) => { - const block: Block = Block.fromBlockData({ - header: { - timestamp: (new Date().getTime() / 1000 | 0), - number: 0, - coinbase: '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', - difficulty, - gasLimit: 8000000 - } - }, { common: vmContext.vmObject().common }) - - vmContext.vm().runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then(() => { - vmContext.addBlock(block) - resolve({}) - }).catch((e) => reject(e)) - }) -} diff --git a/libs/remix-simulator/src/methods/transactions.ts b/libs/remix-simulator/src/methods/transactions.ts index e7fbe6c225..dc6d940e65 100644 --- a/libs/remix-simulator/src/methods/transactions.ts +++ b/libs/remix-simulator/src/methods/transactions.ts @@ -56,7 +56,7 @@ export class Transactions { } this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vmObject()) - this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, { runAsync: false }) + this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, {}) this.txRunnerInstance.vmaccounts = accounts } diff --git a/libs/remix-simulator/src/provider.ts b/libs/remix-simulator/src/provider.ts index ebeca2e803..997accddf9 100644 --- a/libs/remix-simulator/src/provider.ts +++ b/libs/remix-simulator/src/provider.ts @@ -9,7 +9,6 @@ import { methods as miscMethods } from './methods/misc' import { methods as netMethods } from './methods/net' import { Transactions } from './methods/transactions' import { Debug } from './methods/debug' -import { generateBlock } from './genesis' import { VMContext } from './vm-context' export interface JSONRPCRequestPayload { @@ -59,7 +58,6 @@ export class Provider { this.initialized = false this.pendingRequests = [] await this.vmContext.init() - await generateBlock(this.vmContext) await this.Accounts.resetAccounts() this.Transactions.init(this.Accounts.accounts) this.initialized = true diff --git a/libs/remix-simulator/src/vm-context.ts b/libs/remix-simulator/src/vm-context.ts index 5ee49e6cc2..be65caa43c 100644 --- a/libs/remix-simulator/src/vm-context.ts +++ b/libs/remix-simulator/src/vm-context.ts @@ -9,7 +9,8 @@ import { execution } from '@remix-project/remix-lib' const { LogsManager } = execution import { VmProxy } from './VmProxy' import { VM } from '@ethereumjs/vm' -import { Common } from '@ethereumjs/common' +import type { BigIntLike } from '@ethereumjs/util' +import { Common, ConsensusType } from '@ethereumjs/common' import { Trie } from '@ethereumjs/trie' import { DefaultStateManager, StateManager, EthersStateManager, EthersStateManagerOpts } from '@ethereumjs/statemanager' import { StorageDump } from '@ethereumjs/statemanager/dist/interface' @@ -130,6 +131,25 @@ export type CurrentVm = { common: Common } +export class VMCommon extends Common { + + /** + * Override "setHardforkByBlockNumber" to disable updating the original fork state + * + * @param blockNumber + * @param td + * @param timestamp + * @returns The name of the HF set + */ + setHardforkByBlockNumber( + blockNumber: BigIntLike, + td?: BigIntLike, + timestamp?: BigIntLike + ): string { + return this.hardfork() + } +} + /* trigger contextChanged, web3EndpointChanged */ @@ -151,7 +171,7 @@ export class VMContext { constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest') { this.blockGasLimitDefault = 4300000 this.blockGasLimit = this.blockGasLimitDefault - this.currentFork = fork || 'london' + this.currentFork = fork || 'merge' this.nodeUrl = nodeUrl this.blockNumber = blockNumber this.blocks = {} @@ -181,14 +201,28 @@ export class VMContext { } else stateManager = new StateManagerCommonStorageDump() - const common = new Common({ chain: 'mainnet', hardfork }) - const blockchain = new (Blockchain as any)({ common }) + const consensusType = hardfork === 'berlin' || hardfork === 'london' ? ConsensusType.ProofOfWork : ConsensusType.ProofOfStake + const difficulty = consensusType === ConsensusType.ProofOfStake ? 0 : 69762765929000 + + const common = new VMCommon({ chain: 'mainnet', hardfork }) + const genesisBlock: Block = Block.fromBlockData({ + header: { + timestamp: (new Date().getTime() / 1000 | 0), + number: 0, + coinbase: '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', + difficulty, + gasLimit: 8000000 + } + }, { common, hardforkByBlockNumber: false, hardforkByTTD: undefined }) + + const blockchain = await Blockchain.create({ common, validateBlocks: false, validateConsensus: false, genesisBlock }) const eei = new EEI(stateManager, common, blockchain) const evm = new EVM({ common, eei, allowUnlimitedContractSize: true }) const vm = await VM.create({ common, activatePrecompiles: true, + hardforkByBlockNumber: false, stateManager, blockchain, evm @@ -198,6 +232,7 @@ export class VMContext { // VmProxy is used to track the EVM execution (to listen on opcode execution, in order for instance to generate the VM trace) const web3vm = new VmProxy(this) web3vm.setVM(vm) + this.addBlock(genesisBlock, true) return { vm, web3vm, stateManager, common } } @@ -217,7 +252,7 @@ export class VMContext { return this.currentVm } - addBlock (block: Block) { + addBlock (block: Block, genesis?: boolean) { let blockNumber = bigIntToHex(block.header.number) if (blockNumber === '0x') { blockNumber = '0x0' @@ -227,7 +262,7 @@ export class VMContext { this.blocks[blockNumber] = block this.latestBlockNumber = blockNumber - this.logsManager.checkBlock(blockNumber, block, this.web3()) + if (!genesis) this.logsManager.checkBlock(blockNumber, block, this.web3()) } trackTx (txHash, block, tx) { diff --git a/libs/remix-simulator/test/blocks.ts b/libs/remix-simulator/test/blocks.ts index 6c4b0476ac..72b454b8dd 100644 --- a/libs/remix-simulator/test/blocks.ts +++ b/libs/remix-simulator/test/blocks.ts @@ -19,7 +19,7 @@ describe('blocks', () => { const expectedBlock = { baseFeePerGas: 1, - difficulty: '69762765929000', + difficulty: 0, extraData: '0x0', gasLimit: 8000000, gasUsed: 0,