From 6bcf22ba5dccf28d3c5d08bb4e759685e9733df8 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Tue, 20 Apr 2021 17:49:29 -0700 Subject: [PATCH] test: ovm context (#522) * integration tests: ovm context * integration tests: add changeset --- .changeset/perfect-feet-love.md | 5 + .../contracts/OVMContextStorage.sol | 15 ++ integration-tests/contracts/OVMMulticall.sol | 58 ++++++++ integration-tests/test/ovmcontext.spec.ts | 128 ++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 .changeset/perfect-feet-love.md create mode 100644 integration-tests/contracts/OVMContextStorage.sol create mode 100644 integration-tests/contracts/OVMMulticall.sol create mode 100644 integration-tests/test/ovmcontext.spec.ts diff --git a/.changeset/perfect-feet-love.md b/.changeset/perfect-feet-love.md new file mode 100644 index 000000000000..16f87dcc8d93 --- /dev/null +++ b/.changeset/perfect-feet-love.md @@ -0,0 +1,5 @@ +--- +"@eth-optimism/integration-tests": patch +--- + +Add contracts for OVM context test coverage and add tests diff --git a/integration-tests/contracts/OVMContextStorage.sol b/integration-tests/contracts/OVMContextStorage.sol new file mode 100644 index 000000000000..b9bf27cd16d7 --- /dev/null +++ b/integration-tests/contracts/OVMContextStorage.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0; + +contract OVMContextStorage { + mapping (uint256 => uint256) public blockNumbers; + mapping (uint256 => uint256) public timestamps; + uint256 public index = 0; + + fallback() external { + blockNumbers[index] = block.number; + timestamps[index] = block.timestamp; + index++; + } +} diff --git a/integration-tests/contracts/OVMMulticall.sol b/integration-tests/contracts/OVMMulticall.sol new file mode 100644 index 000000000000..e8144d9f3dcb --- /dev/null +++ b/integration-tests/contracts/OVMMulticall.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +/* + +MIT License + +Copyright (c) 2018 Maker Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +*/ + +pragma solidity ^0.7.0; + +pragma experimental ABIEncoderV2; + +/// @title OVMMulticall - Aggregate results from multiple read-only function calls +contract OVMMulticall { + struct Call { + address target; + bytes callData; + } + + function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + returnData = new bytes[](calls.length); + for (uint256 i = 0; i < calls.length; i++) { + (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); + require(success); + returnData[i] = ret; + } + } + + // Helper functions + function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { + timestamp = block.timestamp; + } + + function getCurrentBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; + } + + function getChainID() external view returns (uint256) { + uint256 id; + assembly { + id := chainid() + } + return id; + } +} diff --git a/integration-tests/test/ovmcontext.spec.ts b/integration-tests/test/ovmcontext.spec.ts new file mode 100644 index 000000000000..513892d3bd7a --- /dev/null +++ b/integration-tests/test/ovmcontext.spec.ts @@ -0,0 +1,128 @@ +import { ethers } from 'hardhat' +import { injectL2Context } from '@eth-optimism/core-utils' +import { expect } from 'chai' +import { sleep, l2Provider, l1Provider, getAddressManager } from './shared/utils' +import { OptimismEnv } from './shared/env' +import { getContractFactory } from '@eth-optimism/contracts' +import { Contract, ContractFactory, Wallet, BigNumber } from 'ethers' + +/** + * These tests cover the OVM execution contexts. In the OVM execution + * of a L1 to L2 transaction, both `block.number` and `block.timestamp` + * must be equal to the blocknumber/timestamp of the L1 transaction. + */ +describe('OVM Context: Layer 2 EVM Context', () => { + let address: string + let CanonicalTransactionChain: Contract + let OVMMulticall: Contract + let OVMContextStorage: Contract + + const L1Provider = l1Provider + const L2Provider = injectL2Context(l2Provider) + + before(async () => { + const env = await OptimismEnv.new() + // Create providers and signers + const l1Wallet = env.l1Wallet + const l2Wallet = env.l2Wallet + const addressManager = env.addressManager + + // deploy the contract + const OVMContextStorageFactory = await ethers.getContractFactory( + 'OVMContextStorage', + l2Wallet + ) + + OVMContextStorage = await OVMContextStorageFactory.deploy() + const receipt = await OVMContextStorage.deployTransaction.wait() + address = OVMContextStorage.address + + const ctcAddress = await addressManager.getAddress( + 'OVM_CanonicalTransactionChain' + ) + const CanonicalTransactionChainFactory = getContractFactory( + 'OVM_CanonicalTransactionChain', + ) + + CanonicalTransactionChain = CanonicalTransactionChainFactory + .connect(l1Wallet) + .attach(ctcAddress) + + const OVMMulticallFactory = await ethers.getContractFactory( + 'OVMMulticall', + l2Wallet + ) + + OVMMulticall = await OVMMulticallFactory.deploy() + await OVMMulticall.deployTransaction.wait() + }) + + it('Enqueue: `block.number` and `block.timestamp` have L1 values', async () => { + for (let i = 0; i < 5; i++) { + const l2Tip = await L2Provider.getBlock('latest') + const tx = await CanonicalTransactionChain.enqueue( + OVMContextStorage.address, + 500_000, + '0x' + ) + + // Wait for the enqueue to be ingested + while (true) { + const tip = await L2Provider.getBlock('latest') + if (tip.number === l2Tip.number + 1) { + break + } + await sleep(500) + } + + // Get the receipt + const receipt = await tx.wait() + // The transaction did not revert + expect(receipt.status).to.equal(1) + + // Get the L1 block that the enqueue transaction was in so that + // the timestamp can be compared against the layer two contract + const block = await l1Provider.getBlock(receipt.blockNumber) + + // The contact is a fallback function that keeps `block.number` + // and `block.timestamp` in a mapping based on an index that + // increments each time that there is a transaction. + const blockNumber = await OVMContextStorage.blockNumbers(i) + expect(receipt.blockNumber).to.deep.equal(blockNumber.toNumber()) + const timestamp = await OVMContextStorage.timestamps(i) + expect(block.timestamp).to.deep.equal(timestamp.toNumber()) + } + }) + + /** + * `rollup_getInfo` is a new RPC endpoint that is used to return the OVM + * context. The data returned should match what is actually being used as the + * OVM context. + */ + + it('should return same timestamp and blocknumbers between `eth_call` and `rollup_getInfo`', async () => { + // As atomically as possible, call `rollup_getInfo` and OVMMulticall for the + // blocknumber and timestamp. If this is not atomic, then the sequencer can + // happend to update the timestamp between the `eth_call` and the `rollup_getInfo` + const [info, [, returnData]] = await Promise.all([ + L2Provider.send('rollup_getInfo', []), + OVMMulticall.callStatic.aggregate([ + [ + OVMMulticall.address, + OVMMulticall.interface.encodeFunctionData('getCurrentBlockTimestamp'), + ], + [ + OVMMulticall.address, + OVMMulticall.interface.encodeFunctionData('getCurrentBlockNumber'), + ], + ]), + ]) + + const timestamp = BigNumber.from(returnData[0]) + const blockNumber = BigNumber.from(returnData[1]) + + // TODO: this is a bug and needs to be fixed + //expect(info.ethContext.blockNumber).to.deep.equal(blockNumber.toNumber()) + expect(info.ethContext.timestamp).to.deep.equal(timestamp.toNumber()) + }) +})