diff --git a/packages/rollup-full-node/src/app/test-web3-rpc-handler.ts b/packages/rollup-full-node/src/app/test-web3-rpc-handler.ts index af96d8091a89..581ea0c65a17 100644 --- a/packages/rollup-full-node/src/app/test-web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/test-web3-rpc-handler.ts @@ -55,18 +55,24 @@ export class TestWeb3Handler extends DefaultWeb3Handler { * Override to add some test RPC methods. */ public async handleRequest(method: string, params: any[]): Promise { - if (method === Web3RpcMethods.increaseTimestamp) { - this.assertParameters(params, 1) - this.increaseTimestamp(params[0]) - log.debug(`Set increased timestamp by ${params[0]} seconds.`) - return TestWeb3Handler.successString + switch (method) { + case Web3RpcMethods.increaseTimestamp: + this.assertParameters(params, 1) + this.increaseTimestamp(params[0]) + log.debug(`Set increased timestamp by ${params[0]} seconds.`) + return TestWeb3Handler.successString + case Web3RpcMethods.getTimestamp: + this.assertParameters(params, 0) + return add0x(this.getTimestamp().toString(16)) + case Web3RpcMethods.snapshot: + this.assertParameters(params, 0) + return this.snapshot() + case Web3RpcMethods.revert: + this.assertParameters(params, 1) + return this.revert(params[0]) + default: + return super.handleRequest(method, params) } - if (method === Web3RpcMethods.getTimestamp) { - this.assertParameters(params, 0) - return add0x(this.getTimestamp().toString(16)) - } - - return super.handleRequest(method, params) } /** @@ -94,4 +100,20 @@ export class TestWeb3Handler extends DefaultWeb3Handler { throw new UnsupportedMethodError(msg) } } + + /** + * Takes a snapshot of the current node state. + * @returns The snapshot id that can be used as an parameter of the revert endpoint + */ + private async snapshot(): Promise { + return this.provider.send(Web3RpcMethods.snapshot, []) + } + + /** + * Reverts state to the specified snapshot + * @param The snapshot id of the snapshot to restore + */ + private async revert(snapShotId: string): Promise { + return this.provider.send(Web3RpcMethods.revert, [snapShotId]) + } } diff --git a/packages/rollup-full-node/src/app/web3-rpc-handler.ts b/packages/rollup-full-node/src/app/web3-rpc-handler.ts index 7b60e13ae025..9b7f431a9591 100644 --- a/packages/rollup-full-node/src/app/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/web3-rpc-handler.ts @@ -63,7 +63,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { } protected constructor( - private readonly provider: Web3Provider, + protected readonly provider: Web3Provider, private readonly wallet: Wallet, private readonly executionManager: Contract ) { diff --git a/packages/rollup-full-node/src/types/web3-rpc-handler.ts b/packages/rollup-full-node/src/types/web3-rpc-handler.ts index 2c0afcea7715..9024eba674d3 100644 --- a/packages/rollup-full-node/src/types/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/types/web3-rpc-handler.ts @@ -37,6 +37,8 @@ export enum Web3RpcMethods { getTransactionReceipt = 'eth_getTransactionReceipt', networkVersion = 'net_version', sendRawTransaction = 'eth_sendRawTransaction', + snapshot = 'evm_snapshot', + revert = 'evm_revert', // Test methods: increaseTimestamp = 'evm_increaseTime', diff --git a/packages/rollup-full-node/test/app/handler.spec.ts b/packages/rollup-full-node/test/app/handler.spec.ts index 5656d9e8efbd..ad5a0ffd1fc8 100644 --- a/packages/rollup-full-node/test/app/handler.spec.ts +++ b/packages/rollup-full-node/test/app/handler.spec.ts @@ -86,4 +86,30 @@ describe('Web3Handler', () => { res.should.equal(storageValue) }) }) + + describe('snapshot and revert', () => { + it('should fail (snapshot and revert should only be available in the TestHandler)', async () => { + const httpProvider = new ethers.providers.JsonRpcProvider(baseUrl) + await new Promise((resolve, reject) => { + httpProvider.send('evm_snapshot', []).catch((error) => { + error.message.should.equal('Method not found') + resolve() + }) + }) + + await new Promise((resolve, reject) => { + httpProvider.send('evm_snapshot', []).catch((error) => { + error.message.should.equal('Method not found') + resolve() + }) + }) + + await new Promise((resolve, reject) => { + httpProvider.send('evm_revert', ['0x01']).catch((error) => { + error.message.should.equal('Method not found') + resolve() + }) + }) + }) + }) }) diff --git a/packages/rollup-full-node/test/app/test-handler.spec.ts b/packages/rollup-full-node/test/app/test-handler.spec.ts index b8881c59cfbf..5361226c8dd0 100644 --- a/packages/rollup-full-node/test/app/test-handler.spec.ts +++ b/packages/rollup-full-node/test/app/test-handler.spec.ts @@ -1,15 +1,25 @@ import '../setup' /* External Imports */ import { add0x, getLogger, remove0x } from '@eth-optimism/core-utils' +import { ethers, ContractFactory } from 'ethers' /* Internal Imports */ -import { Web3RpcMethods, TestWeb3Handler } from '../../src' +import { + Web3RpcMethods, + TestWeb3Handler, + FullnodeRpcServer, + DefaultWeb3Handler, +} from '../../src' +import * as SimpleStorage from '../contracts/build/SimpleStorage.json' const log = getLogger('test-web3-handler', true) const secondsSinceEopch = (): number => { return Math.round(Date.now() / 1000) } +const host = '0.0.0.0' +const port = 9999 +const baseUrl = `http://${host}:${port}` describe('TestHandler', () => { let testHandler: TestWeb3Handler @@ -60,4 +70,55 @@ describe('TestHandler', () => { ) }) }) + + describe('Snapshot and revert', () => { + it('should revert state', async () => { + const testRpcServer = new FullnodeRpcServer(testHandler, host, port) + + testRpcServer.listen() + const httpProvider = new ethers.providers.JsonRpcProvider(baseUrl) + const executionManagerAddress = await httpProvider.send( + 'ovm_getExecutionManagerAddress', + [] + ) + const privateKey = '0x' + '60'.repeat(32) + const wallet = new ethers.Wallet(privateKey, httpProvider) + log.debug('Wallet address:', wallet.address) + const factory = new ContractFactory( + SimpleStorage.abi, + SimpleStorage.bytecode, + wallet + ) + + const simpleStorage = await factory.deploy() + const deploymentTxReceipt = await wallet.provider.getTransactionReceipt( + simpleStorage.deployTransaction.hash + ) + + const storageKey = '0x' + '01'.repeat(32) + const storageValue = '0x' + '02'.repeat(32) + const storageValue2 = '0x' + '03'.repeat(32) + // Set storage with our new storage elements + const networkInfo = await httpProvider.getNetwork() + const tx = await simpleStorage.setStorage( + executionManagerAddress, + storageKey, + storageValue + ) + const snapShotId = await httpProvider.send('evm_snapshot', []) + const tx2 = await simpleStorage.setStorage( + executionManagerAddress, + storageKey, + storageValue2 + ) + const receipt = await httpProvider.getTransactionReceipt(tx.hash) + const receipt2 = await httpProvider.getTransactionReceipt(tx2.hash) + const response2 = await httpProvider.send('evm_revert', [snapShotId]) + const res = await simpleStorage.getStorage( + executionManagerAddress, + storageKey + ) + res.should.equal(storageValue) + }) + }) })