Commit f63e3231 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

Merge pull request #5926 from ethereum-optimism/cleanup/remove-old-itests

integration-tests: deprecate
parents cccce791 790c333e
......@@ -93,7 +93,6 @@ jobs:
- "packages/drippie-mon/node_modules"
- "packages/fault-detector/node_modules"
- "packages/hardhat-deploy-config/node_modules"
- "packages/integration-tests-bedrock/node_modules"
- "packages/message-relayer/node_modules"
- "packages/migration-data/node_modules"
- "packages/replica-healthcheck/node_modules"
......@@ -693,10 +692,6 @@ jobs:
name: Check sdk
command: npx depcheck
working_directory: packages/sdk
- run:
name: Check integration-tests
command: npx depcheck
working_directory: integration-tests
atst-tests:
docker:
......@@ -878,7 +873,7 @@ jobs:
steps:
- checkout
- check-changed:
patterns: op-(.+),packages,integration-tests
patterns: op-(.+),packages
- run:
name: Install latest golang
command: |
......@@ -1005,35 +1000,6 @@ jobs:
docker logs ops-bedrock-op-proposer-1 || echo "No logs."
when: on_fail
integration-tests:
machine:
image: ubuntu-2204:2022.07.1
environment:
DOCKER_BUILDKIT: 1
parallelism: 3
steps:
- checkout
- check-changed:
patterns: l2geth,common-ts,contracts,core-utils,message-relayer,data-transport-layer,replica-healthcheck,sdk,batch-submitter,gas-oracle,bss-core,integration-tests
- run:
name: Bring up the stack
command: |
docker-compose build --progress=plain
docker-compose up -d --scale replica_healthcheck=1
working_directory: ops
- run:
name: Wait for sequencer
command: bash scripts/wait-for-sequencer.sh
working_directory: ops
- run:
name: Run integration tests
command: |
circleci tests glob "../integration-tests/test/*.spec.ts" | circleci tests split | tee splits.txt
docker-compose run integration_tests $(cat splits.txt)
working_directory: ops
- run:
command: echo "Done."
semgrep-scan:
parameters:
diff_branch:
......@@ -1315,7 +1281,6 @@ workflows:
binary_name: op-heartbeat
working_directory: op-heartbeat
- geth-tests
- integration-tests
- semgrep-scan
- go-mod-tidy
- fuzz-op-node
......
......@@ -2,7 +2,6 @@
/batch-submitter @ethereum-optimism/legacy-reviewers
/bss-core @ethereum-optimism/legacy-reviewers
/gas-oracle @ethereum-optimism/legacy-reviewers
/integration-tests @ethereum-optimism/legacy-reviewers
/l2geth @ethereum-optimism/legacy-reviewers
/l2geth-exporter @ethereum-optimism/legacy-reviewers
/packages/actor-tests @ethereum-optimism/legacy-reviewers
......
......@@ -394,33 +394,6 @@ jobs:
push: true
tags: ethereumoptimism/deployer-bedrock:${{ needs.canary-publish.outputs.canary-docker-tag }}
integration_tests:
name: Publish Integration tests ${{ needs.canary-publish.outputs.integration-tests }}
needs: canary-publish
if: needs.canary-publish.outputs.integration-tests != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: integration-tests
push: true
tags: ethereumoptimism/integration-tests:${{ needs.canary-publish.outputs.canary-docker-tag }}
replica-healthcheck:
name: Publish Replica Healthcheck Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......
......@@ -507,33 +507,6 @@ jobs:
push: true
tags: ethereumoptimism/balance-monitor:${{ needs.release.outputs.balance-monitor }},ethereumoptimism/balance-monitor:latest
integration_tests:
name: Publish Integration tests ${{ needs.release.outputs.integration-tests }}
needs: release
if: needs.release.outputs.integration-tests != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: integration-tests
push: true
tags: ethereumoptimism/integration-tests:${{ needs.release.outputs.integration-tests }},ethereumoptimism/integration-tests:latest
replica-healthcheck:
name: Publish Replica Healthcheck Version ${{ needs.release.outputs.replica-healthcheck }}
needs: release
......
......@@ -30,9 +30,6 @@ packages/contracts-periphery/forge-artifacts*
packages/data-transport-layer/db
packages/integration-tests-bedrock/cache
packages/integration-tests-bedrock/artifacts
packages/contracts-bedrock/deployments/devnetL1
packages/contracts-bedrock/deployments/anvil
......
......@@ -129,7 +129,6 @@ This will build the following containers:
* [`verifier`](https://hub.docker.com/r/ethereumoptimism/go-ethereum): L2 geth node running in Verifier mode
* [`relayer`](https://hub.docker.com/r/ethereumoptimism/message-relayer): helper process that relays messages between L1 and L2
* [`batch_submitter`](https://hub.docker.com/r/ethereumoptimism/batch-submitter-service): service that submits batches of Sequencer transactions to the L1 chain
* [`integration_tests`](https://hub.docker.com/r/ethereumoptimism/integration-tests): integration tests in a box
If you want to make a change to a container, you'll need to take it down and rebuild it.
For example, if you make a change in l2geth:
......@@ -193,16 +192,6 @@ cd packages/package-to-test
yarn test
```
#### Running integration tests
Follow above instructions for building the whole stack.
Build and run the integration tests:
```bash
cd integration-tests
yarn build
yarn test:integration
```
#### Running contract static analysis
We perform static analysis with [`slither`](https://github.com/crytic/slither).
......
......@@ -65,7 +65,6 @@ Refer to the Directory Structure section below to understand which packages are
├── <a href="./gas-oracle">gas-oracle</a>: Service for updating L1 gas prices on L2
├── <a href="./indexer">indexer</a>: indexes and syncs transactions
├── <a href="./infra/op-replica">infra/op-replica</a>: Deployment examples and resources for running an Optimism replica
├── <a href="./integration-tests">integration-tests</a>: Various integration tests for the Optimism network
├── <a href="./l2geth">l2geth</a>: Optimism client software, a fork of <a href="https://github.com/ethereum/go-ethereum/tree/v1.9.10">geth v1.9.10</a>
├── <a href="./l2geth-exporter">l2geth-exporter</a>: A prometheus exporter to collect/serve metrics from an L2 geth node
├── <a href="./op-exporter">op-exporter</a>: A prometheus exporter to collect/serve metrics from an Optimism node
......
ignores: [
"@openzeppelin/contracts",
"@types/mocha",
"@types/rimraf",
"@uniswap/v3-core",
"mocha",
"typescript",
]
\ No newline at end of file
# only need to fill these out if you want to test against a prod network
PRIVATE_KEY=
L1_URL=
L2_URL=
ADDRESS_MANAGER=
L1_CROSS_DOMAIN_MESSENGER=
L1_STANDARD_BRIDGE=
STATE_COMMITMENT_CHAIN=
CANONICAL_TRANSACTION_CHAIN=
BOND_MANAGER=
L2_CHAINID=
DTL_ENQUEUE_CONFIRMATIONS=
OVMCONTEXT_SPEC_NUM_TXS=1
# Can be set to true below if the withdrawal window is short enough
RUN_WITHDRAWAL_TESTS=false
RUN_DEBUG_TRACE_TESTS=false
RUN_REPLICA_TESTS=false
RUN_HEALTHCHECK_TESTS=false
RUN_STRESS_TESTS=false
# Can be configured up or down as necessary
MOCHA_TIMEOUT=300000
# Set to true to make Mocha stop after the first failed test.
MOCHA_BAIL=false
module.exports = {
extends: '../.eslintrc.js',
}
module.exports = {
...require('../.prettierrc.js'),
}
# @eth-optimism/integration-tests
## 0.5.22
### Patch Changes
- 1d3c749a2: Bumps the version of ts-node used
- 1d3c749a2: Updates the version of TypeScript
## 0.5.21
### Patch Changes
- a3242d4f: Fix erc721 factory to match erc21 factory
## 0.5.20
### Patch Changes
- 02c457a5: Removes NFT refund logic if withdrawals fail.
## 0.5.19
### Patch Changes
- 5c3f2b1f: Fixes NFT bridge related contracts in response to the OpenZeppelin audit. Updates tests to support these changes, including integration tests.
## 0.5.18
### Patch Changes
- 7215f4ce: Bump ethers to 5.7.0 globally
## 0.5.17
### Patch Changes
- d97df13a: Modularize the itests away from depending on api of messenger
## 0.5.16
### Patch Changes
- 977493bc: Update SDK version and usage to account for new constructor
## 0.5.15
### Patch Changes
- 29ff7462: Revert es target back to 2017
## 0.5.14
### Patch Changes
- f688a631: integration-tests: Override default bridge adapters
- d18ae135: Updates all ethers versions in response to BN.js bug
## 0.5.13
### Patch Changes
- 412688d5: Replace calls to getNetwork() with getChainId util
## 0.5.12
### Patch Changes
- 53fac1df: Facilitate actor testing on nightly
## 0.5.11
### Patch Changes
- 36a91c30: Fix various actor tests
## 0.5.10
### Patch Changes
- db02f97f: Add tests for system addrs on verifiers/replicas
## 0.5.9
### Patch Changes
- 5bf390b4: Update chainid
- c1957126: Update Dockerfile to use Alpine
- d9a51154: Bump to hardhat@2.9.1
## 0.5.8
### Patch Changes
- 88807f03: Add integration test for healthcheck server
## 0.5.7
### Patch Changes
- 88601cb7: Refactored Dockerfiles
## 0.5.6
### Patch Changes
- 962f36e4: Add support for system addresses
- d6e309be: Add test coverage for zlib compressed batches
- 386df4dc: Replaces contract references in integration tests with SDK CrossChainMessenger objects.
## 0.5.5
### Patch Changes
- 45642dc8: Replaces l1Provider and l2Provider with env.l1Provider and env.l2Provider respectively.
## 0.5.4
### Patch Changes
- dc5f6517: Deletes watcher-utils.ts. Moves it's utilities into env.ts.
- dcdcc757: Removes message relaying utilities from the Message Relayer, to be replaced by the SDK
## 0.5.3
### Patch Changes
- a8a74a98: Remove Watcher usage from itests
- e2ad8653: Support non-well-known networks
- 152df378: Use new asL2Provider function for integration tests
- 748c04ab: Updates integration tests to use the SDK for bridged token tests
- 8cb2535b: Skip an unreliable test
## 0.5.2
### Patch Changes
- d6c2830a: Increase withdrawal test timeout
- 0293749e: Add an integration test showing the infeasability of withdrawing a fake token in exchange for a legitimate token.
- a135aa3d: Updates integration tests to include a test for syncing a Verifier from L1
- 0bb11484: Remove nightly itests - not needed anymore
- ba14c59d: Updates various ethers dependencies to their latest versions
- a135aa3d: Add verifier integration tests
- edb21845: Updates integration tests to start using SDK
## 0.5.1
### Patch Changes
- e631c39c: Add in berlin hardfork tests
## 0.5.0
### Minor Changes
- c1e923f9: Updates to work with a live network
### Patch Changes
- 968fb38d: Use hardhat-ethers for importing factories in integration tests
- a7fbafa8: Split OVMMulticall.sol into Multicall.sol & OVMContext.sol
## 0.4.2
### Patch Changes
- 5787a55b: Updates to support nightly actor tests
- dad6fd9b: Update timestamp assertion for new logic
## 0.4.1
### Patch Changes
- a8013127: Remove sync-tests as coverage lives in itests now
- b1fa3f33: Enforce fees in docker-compose setup and test cases for fee too low and fee too high
- 4559a824: Pass through starting block height to dtl
## 0.4.0
### Minor Changes
- 3ce64804: Add actor tests
## 0.3.3
### Patch Changes
- 0ab37fc9: Update to node.js version 16
## 0.3.2
### Patch Changes
- d141095c: Allow for unprotected transactions
## 0.3.1
### Patch Changes
- 243f33e5: Standardize package json file format
## 0.3.0
### Minor Changes
- e03dcead: Start refactor to new version of the OVM
- e4a1129c: Adds aliasing to msg.sender and tx.origin to avoid xdomain attacks
- 3f590e33: Remove the "OVM" Prefix from contract names
- 872f5976: Removes various unused OVM contracts
- 92c9692d: Opcode tweaks. Coinbase returns SequencerFeeVault address. Difficulty returns zero.
- 1e63ffa0: Refactors and simplifies OVM_ETH usage
- b56dd079: Updates the deployment process to correctly set all constants and adds more integration tests
- 81ccd6e4: `regenesis/0.5.0` release
- f38b8000: Removes ERC20 and WETH9 features from OVM_ETH
- 3605b963: Adds refactored support for the L1MESSAGESENDER opcode
### Patch Changes
- 299a459e: Introduces a new opcode L1BLOCKNUMBER to replace old functionality where blocknumber would return the L1 block number and the L2 block number was inaccessible.
- 343da72a: Add tests for optimistic ethereum related fields to the receipt
- 7b761af5: Add updated fee scheme integration tests
- b70ee70c: upgraded to solidity 0.8.9
- a98a1884: Fixes dependencies instead of using caret constraints
## 0.2.4
### Patch Changes
- 6d3e1d7f: Update dependencies
## 0.2.3
### Patch Changes
- 918c08ca: Bump ethers dependency to 5.4.x to support eip1559
## 0.2.2
### Patch Changes
- c73c3939: Update the typescript version to `4.3.5`
## 0.2.1
### Patch Changes
- f1dc8b77: Add various stress tests
## 0.2.0
### Minor Changes
- aa6fad84: Various updates to integration tests so that they can be executed against production networks
## 0.1.2
### Patch Changes
- b107a032: Make expectApprox more readable by passing optional args as an object with well named keys
## 0.1.1
### Patch Changes
- 40b99a6e: Add new RPC endpoint `rollup_gasPrices`
## 0.1.0
### Minor Changes
- e04de624: Add support for ovmCALL with nonzero ETH value
### Patch Changes
- 25f09abd: Adds ERC1271 support to default contract account
- 5fc728da: Add a new Standard Token Bridge, to handle deposits and withdrawals of any ERC20 token.
For projects developing a custom bridge, if you were previously importing `iAbs_BaseCrossDomainMessenger`, you should now
import `iOVM_CrossDomainMessenger`.
- c43b33ec: Add WETH9 compatible deposit and withdraw functions to OVM_ETH
- e045f582: Adds new SequencerFeeVault contract to store generated fees
- b8e2d685: Add replica sync test to integration tests; handle 0 L2 blocks in DTL
## 0.0.7
### Patch Changes
- d1680052: Reduce test timeout from 100 to 20 seconds
- c2b6e14b: Implement the latest fee spec such that the L2 gas limit is scaled and the tx.gasPrice/tx.gasLimit show correctly in metamask
- 77108d37: Add verifier sync test and extra docker-compose functions
## 0.0.6
### Patch Changes
- f091e86: Fix to ensure that L1 => L2 success status is reflected correctly in receipts
- f880479: End to end fee integration with recoverable L2 gas limit
## 0.0.5
### Patch Changes
- 467d6cb: Adds a test for contract deployments that run out of gas
## 0.0.4
### Patch Changes
- b799caa: Add support for parsed revert reasons in DoEstimateGas
- b799caa: Update minimum response from estimate gas
- b799caa: Add value transfer support to ECDSAContractAccount
- b799caa: Update expected gas prices based on minimum of 21k value
## 0.0.3
### Patch Changes
- 6daa408: update hardhat versions so that solc is resolved correctly
- 5b9be2e: Correctly set the OVM context based on the L1 values during `eth_call`. This will also set it during `eth_estimateGas`. Add tests for this in the integration tests
## 0.0.2
### Patch Changes
- 6bcf22b: Add contracts for OVM context test coverage and add tests
(The MIT License)
Copyright 2020-2021 Optimism
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.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# @eth-optimism/integration-tests
Note that these tests are ran against the legacy system, see `op-e2e` for
the bedrock test suite.
## Setup
Follow installation + build instructions in the [primary README](../README.md).
Then, run:
```bash
yarn build
```
## Running integration tests
### Testing a live network
Testing on a live network is a bit more complicated than testing locally. You'll need the following in order to do so:
1. A pre-funded wallet with at least 40 ETH.
2. URLs to an L1 and L2 node.
3. The address of the address manager contract.
4. The chain ID of the L2.
Once you have all the necessary info, create a `.env` file like the one in `.env.example` and fill it in with the values above. Then, run:
```bash
yarn test:integration:live
```
This will take quite a long time. Kovan, for example, takes about 30 minutes to complete.
You can also set environment variables on the command line instead of inside `.env` if you want:
```bash
L1_URL=whatever L2_URL=whatever yarn test:integration:live
```
To run the Uniswap integration tests against a deployed set of Uniswap contracts, add the following env vars:
```
UNISWAP_POSITION_MANAGER_ADDRESS=<non fungible position manager address>
UNISWAP_ROUTER_ADDRESS=<router address>
```
## Running actor tests
Actor tests use the same environment variables as the integration tests, so set up your `.env` file if you haven't
already. Then, run `yarn test:actor <args>` to run the tests. Note that it will be **very expensive** to run the actor
tests against mainnet, and that the tests can take a while to complete.
See [actor-tests/README.md](actor-tests/README.md) for information on actor tests.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { Reverter } from './Reverter.sol';
contract ConstructorReverter is Reverter {
constructor() {
doRevert();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract ERC20 {
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
uint256 constant private MAX_UINT256 = 2**256 - 1;
mapping (address => uint256) public balances;
mapping (address => mapping (address => uint256)) public allowed;
/*
NOTE:
The following variables are OPTIONAL vanities. One does not have to include them.
They allow one to customise the token contract & in no way influences the core functionality.
Some wallets/interfaces might not even bother to look at this information.
*/
string public name; //fancy name: eg OVM Coin
uint8 public decimals; //How many decimals to show.
string public symbol; //An identifier: eg OVM
uint256 public totalSupply;
constructor(
uint256 _initialAmount,
string memory _tokenName,
uint8 _decimalUnits,
string memory _tokenSymbol
) public {
balances[msg.sender] = _initialAmount; // Give the creator all initial tokens
totalSupply = _initialAmount; // Update total supply
name = _tokenName; // Set the name for display purposes
decimals = _decimalUnits; // Amount of decimals for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
}
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balances[msg.sender] >= _value, "insufficient balance");
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
uint256 allowance = allowed[_from][msg.sender];
require(balances[_from] >= _value && allowance >= _value, "bad allowance");
balances[_to] += _value;
balances[_from] -= _value;
if (allowance < MAX_UINT256) {
allowed[_from][msg.sender] -= _value;
}
emit Transfer(_from, _to, _value);
return true;
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) public returns (bool success) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return allowed[_owner][_spender];
}
function destroy() public {
selfdestruct(payable(msg.sender));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract FakeL2StandardERC20 {
address public immutable l1Token;
address public immutable l2Bridge;
constructor(address _l1Token, address _l2Bridge) {
l1Token = _l1Token;
l2Bridge = _l2Bridge;
}
// Burn will be called by the L2 Bridge to burn the tokens we are bridging to L1
function burn(address, uint256) external {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { OptimismMintableERC721 } from "@eth-optimism/contracts-bedrock/contracts/universal/OptimismMintableERC721.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
contract FakeOptimismMintableERC721 is OptimismMintableERC721 {
constructor(
address _bridge,
address _remoteToken,
uint256 _remoteChainId
) OptimismMintableERC721(
_bridge,
_remoteChainId,
_remoteToken,
"FakeERC721",
"FAKE"
) {}
function safeMint(address to, uint256 tokenId) external override {
_safeMint(to, tokenId);
}
// Burn will be called by the L2 Bridge to burn the NFT we are bridging to L1
function burn(address, uint256 tokenId) external override {
_burn(tokenId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
pragma experimental ABIEncoderV2;
// https://github.com/makerdao/multicall/blob/master/src/Multicall.sol
/// @title Multicall - Aggregate results from multiple read-only function calls
/// @author Michael Elliot <mike@makerdao.com>
/// @author Joshua Levine <joshua@makerdao.com>
/// @author Nick Johnson <arachnid@notdot.net>
contract Multicall {
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 getEthBalance(address addr) public view returns (uint256 balance) {
balance = addr.balance;
}
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
blockHash = blockhash(blockNumber);
}
function getLastBlockHash() public view returns (bytes32 blockHash) {
blockHash = blockhash(block.number - 1);
}
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
timestamp = block.timestamp;
}
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
difficulty = block.difficulty;
}
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
gaslimit = block.gaslimit;
}
function getCurrentBlockCoinbase() public view returns (address coinbase) {
coinbase = block.coinbase;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract NFT is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("OVM NFT", "ONFT") {
}
function give() public {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
}
}
\ No newline at end of file
// 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.8.9;
// Can't do this until the package is published.
//import { iOVM_L1BlockNumber } from "@eth-optimism/contracts/iOVM_L1BlockNumber";
interface iOVM_L1BlockNumber {
function getL1BlockNumber() external view returns (uint256);
}
/// @title OVMContext - Helper Functions
contract OVMContext {
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
timestamp = block.timestamp;
}
function getCurrentL1BlockNumber() public view returns (uint256) {
return iOVM_L1BlockNumber(
0x4200000000000000000000000000000000000013
).getL1BlockNumber();
}
function getCurrentBlockNumber() public view returns (uint256) {
return block.number;
}
function getChainID() external view returns (uint256) {
uint256 id;
assembly {
id := chainid()
}
return id;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {OVMContext} from "./OVMContext.sol";
contract OVMContextStorage is OVMContext {
mapping(uint256 => uint256) public l1BlockNumbers;
mapping(uint256 => uint256) public blockNumbers;
mapping(uint256 => uint256) public timestamps;
mapping(uint256 => uint256) public difficulty;
mapping(uint256 => address) public coinbases;
uint256 public index = 0;
fallback() external {
l1BlockNumbers[index] = getCurrentL1BlockNumber();
blockNumbers[index] = getCurrentBlockNumber();
timestamps[index] = getCurrentBlockTimestamp();
difficulty[index] = block.difficulty;
coinbases[index] = block.coinbase;
index++;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract Precompiles {
function expmod(uint256 base, uint256 e, uint256 m) public returns (uint256 o) {
assembly {
// define pointer
let p := mload(0x40)
// store data assembly-favouring ways
mstore(p, 0x20) // Length of Base
mstore(add(p, 0x20), 0x20) // Length of Exponent
mstore(add(p, 0x40), 0x20) // Length of Modulus
mstore(add(p, 0x60), base) // Base
mstore(add(p, 0x80), e) // Exponent
mstore(add(p, 0xa0), m) // Modulus
if iszero(staticcall(sub(gas(), 2000), 0x05, p, 0xc0, p, 0x20)) {
revert(0, 0)
}
// data
o := mload(p)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internall call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
/**
* @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internall call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback () external payable virtual {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive () external payable virtual {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overriden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract Reverter {
string constant public revertMessage = "This is a simple reversion.";
function doRevert() public pure {
revert(revertMessage);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract SelfDestruction {
bytes32 public data = 0x0000000000000000000000000000000000000000000000000000000061626364;
function setData(bytes32 _data) public {
data = _data;
}
function destruct() public {
address payable self = payable(address(this));
selfdestruct(self);
}
}
pragma solidity ^0.8.9;
contract ICrossDomainMessenger {
address public xDomainMessageSender;
}
contract SimpleStorage {
address public msgSender;
address public txOrigin;
address public xDomainSender;
bytes32 public value;
uint256 public totalCount;
function setValue(bytes32 newValue) public {
msgSender = msg.sender;
txOrigin = tx.origin;
xDomainSender = ICrossDomainMessenger(msg.sender)
.xDomainMessageSender();
value = newValue;
totalCount++;
}
function setValueNotXDomain(bytes32 newValue) public {
msgSender = msg.sender;
txOrigin = tx.origin;
value = newValue;
totalCount++;
}
function dumbSetValue(bytes32 newValue) public {
value = newValue;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract StateDOS {
bool public hasRun = false;
function attack() public {
// jumpdest ; jump label, start of loop
// gas ; get a 'random' value on the stack
// extcodesize ; trigger trie lookup
// pop ; ignore the extcodesize result
// push1 0x00 ; jump label dest
// jump ; jump back to start
assembly {
let thegas := gas()
// While greater than 23000 gas. This will let us SSTORE at the end.
for { } gt(thegas, 0x59D8) { } {
thegas := gas()
let ignoredext := extcodesize(thegas)
}
}
hasRun = true; // Sanity check
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract TestOOG {
function runOutOfGas() public {
bytes32 h;
for (uint256 i = 0; i < 100000; i++) {
h = keccak256(abi.encodePacked(h));
}
}
}
contract TestOOGInConstructor is TestOOG {
constructor() {
runOutOfGas();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract ValueContext {
function getSelfBalance() external payable returns(uint256) {
uint selfBalance;
assembly {
selfBalance := selfbalance()
}
return selfBalance;
}
function getAddressThisBalance() external view returns(uint256) {
return address(this).balance;
}
function getBalance(
address _address
) external payable returns(uint256) {
return _address.balance;
}
function getCallValue() public payable returns(uint256) {
return msg.value;
}
function getCaller() external view returns (address){
return msg.sender;
}
}
contract ValueCalls is ValueContext {
receive() external payable {}
function nonPayable() external {}
function simpleSend(
address _address,
uint _value
) external payable returns (bool, bytes memory) {
return sendWithData(_address, _value, hex"");
}
function sendWithDataAndGas(
address _address,
uint _value,
uint _gasLimit,
bytes memory _calldata
) public returns (bool, bytes memory) {
return _address.call{value: _value, gas: _gasLimit}(_calldata);
}
function sendWithData(
address _address,
uint _value,
bytes memory _calldata
) public returns (bool, bytes memory) {
return _address.call{value: _value}(_calldata);
}
function verifyCallValueAndRevert(
uint256 _expectedValue
) external payable {
bool correct = _checkCallValue(_expectedValue);
// do the opposite of expected if the value is wrong.
if (correct) {
revert("expected revert");
} else {
return;
}
}
function verifyCallValueAndReturn(
uint256 _expectedValue
) external payable {
bool correct = _checkCallValue(_expectedValue);
// do the opposite of expected if the value is wrong.
if (correct) {
return;
} else {
revert("unexpected revert");
}
}
function delegateCallToCallValue(
address _valueContext
) public payable returns(bool, bytes memory) {
bytes memory data = abi.encodeWithSelector(ValueContext.getCallValue.selector);
return _valueContext.delegatecall(data);
}
function delegateCallToAddressThisBalance(
address _valueContext
) public payable returns(bool, bytes memory) {
bytes memory data = abi.encodeWithSelector(ValueContext.getAddressThisBalance.selector);
return _valueContext.delegatecall(data);
}
function _checkCallValue(
uint256 _expectedValue
) internal returns(bool) {
return getCallValue() == _expectedValue;
}
}
contract ValueGasMeasurer {
function measureGasOfTransferingEthViaCall(
address target,
uint256 value,
uint256 gasLimit
) public returns(uint256) {
uint256 gasBefore = gasleft();
assembly {
pop(call(gasLimit, target, value, 0, 0, 0, 0))
}
return gasBefore - gasleft();
}
}
contract PayableConstant {
function returnValue() external payable returns(uint256) {
return 42;
}
}
contract SendETHAwayAndDelegateCall {
function emptySelfAndDelegateCall(
address _delegateTo,
bytes memory _data
) public payable returns (bool, bytes memory) {
address(0).call{value: address(this).balance}(_data);
return _delegateTo.delegatecall(_data);
}
}
#!/bin/bash
git clone --depth=1 --branch develop https://github.com/Synthetixio/synthetix.git
cd synthetix
npm install
npx hardhat --config ./hardhat.config.js test:integration:dual --compile --deploy
import { HardhatUserConfig } from 'hardhat/types'
// Hardhat plugins
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
import 'hardhat-gas-reporter'
import './tasks/check-block-hashes'
import { envConfig } from './test/shared/utils'
const enableGasReport = !!process.env.ENABLE_GAS_REPORT
const config: HardhatUserConfig = {
networks: {
optimism: {
url: process.env.L2_URL || 'http://localhost:8545',
},
},
mocha: {
timeout: envConfig.MOCHA_TIMEOUT,
bail: envConfig.MOCHA_BAIL,
},
solidity: {
compilers: [
{
version: '0.7.6',
settings: {},
},
{
version: '0.8.9',
settings: {
optimizer: { enabled: true, runs: 200 },
metadata: {
bytecodeHash: 'none',
},
outputSelection: {
'*': {
'*': ['storageLayout'],
},
},
},
},
{
version: '0.8.15',
settings: {
optimizer: { enabled: true, runs: 200 },
metadata: {
bytecodeHash: 'none',
},
outputSelection: {
'*': {
'*': ['storageLayout'],
},
},
},
},
],
},
gasReporter: {
enabled: enableGasReport,
currency: 'USD',
gasPrice: 100,
outputFile: process.env.CI ? 'gas-report.txt' : undefined,
},
}
export default config
{
"private": true,
"name": "@eth-optimism/integration-tests",
"version": "0.5.22",
"description": "[Optimism] Integration tests",
"scripts": {
"lint": "yarn lint:fix && yarn lint:check",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint . --max-warnings=0",
"build": "hardhat compile",
"test:integration": "hardhat --network optimism test",
"test:actor": "IS_LIVE_NETWORK=true ts-node actor-tests/lib/runner.ts",
"test:integration:live": "NO_NETWORK=true IS_LIVE_NETWORK=true hardhat --network optimism test",
"clean": "rimraf cache artifacts",
"pre-commit": "lint-staged"
},
"keywords": [
"optimism",
"ethereum",
"integration",
"tests"
],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/integration-tests#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"devDependencies": {
"@babel/eslint-parser": "^7.5.4",
"@eth-optimism/contracts": "^0.6.0",
"@eth-optimism/contracts-bedrock": "0.14.0",
"@eth-optimism/contracts-periphery": "^1.0.8",
"@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.1.0",
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"@ethersproject/transactions": "^5.7.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^4.4.0",
"@types/chai": "^4.2.18",
"@types/chai-as-promised": "^7.1.4",
"@types/mocha": "^8.2.2",
"@types/rimraf": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^5.45.1",
"@typescript-eslint/parser": "^5.45.1",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.0.1",
"@uniswap/v3-sdk": "^3.6.2",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"dotenv": "^10.0.0",
"envalid": "^7.1.0",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^42.0.0",
"ethereum-waffle": "^3.3.0",
"ethers": "^5.7.0",
"hardhat": "^2.9.6",
"hardhat-gas-reporter": "^1.0.4",
"lint-staged": "11.0.0",
"mocha": "^8.4.0",
"node-fetch": "^2.6.7",
"rimraf": "^3.0.2",
"typescript": "^4.9.3",
"uniswap-v3-deploy-plugin": "^0.1.0"
}
}
import { task } from 'hardhat/config'
import { providers } from 'ethers'
import { getChainId } from '@eth-optimism/core-utils'
import { die, logStderr } from '../test/shared/utils'
task(
'check-block-hashes',
'Compares the block hashes of two different replicas.'
)
.addPositionalParam('replicaA', 'The first replica')
.addPositionalParam('replicaB', 'The second replica')
.setAction(async ({ replicaA, replicaB }) => {
const providerA = new providers.JsonRpcProvider(replicaA)
const providerB = new providers.JsonRpcProvider(replicaB)
let chainIdA
let chainIdB
try {
chainIdA = await getChainId(providerA)
} catch (e) {
console.error(`Error getting network chainId from ${replicaA}:`)
die(e)
}
try {
chainIdB = await getChainId(providerB)
} catch (e) {
console.error(`Error getting network chainId from ${replicaB}:`)
die(e)
}
if (chainIdA !== chainIdB) {
die('Chain IDs do not match')
return
}
logStderr('Getting block height.')
const heightA = await providerA.getBlockNumber()
const heightB = await providerB.getBlockNumber()
const endHeight = Math.min(heightA, heightB)
logStderr(`Chose block height: ${endHeight}`)
for (let n = endHeight; n >= 1; n--) {
const blocks = await Promise.all([
providerA.getBlock(n),
providerB.getBlock(n),
])
const hashA = blocks[0].hash
const hashB = blocks[1].hash
if (hashA !== hashB) {
console.log(`HASH MISMATCH! block=${n} a=${hashA} b=${hashB}`)
continue
}
console.log(`HASHES OK! block=${n} hash=${hashA}`)
return
}
})
/* Imports: External */
import { Contract, ContractFactory } from 'ethers'
import { ethers } from 'hardhat'
import { MessageDirection, MessageStatus } from '@eth-optimism/sdk'
import {
applyL1ToL2Alias,
awaitCondition,
sleep,
} from '@eth-optimism/core-utils'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import {
DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2,
envConfig,
withdrawalTest,
} from './shared/utils'
describe('Basic L1<>L2 Communication', async () => {
let Factory__L1SimpleStorage: ContractFactory
let Factory__L2SimpleStorage: ContractFactory
let Factory__L2Reverter: ContractFactory
let L1SimpleStorage: Contract
let L2SimpleStorage: Contract
let L2Reverter: Contract
let env: OptimismEnv
before(async () => {
env = await OptimismEnv.new()
Factory__L1SimpleStorage = await ethers.getContractFactory(
'SimpleStorage',
env.l1Wallet
)
Factory__L2SimpleStorage = await ethers.getContractFactory(
'SimpleStorage',
env.l2Wallet
)
Factory__L2Reverter = await ethers.getContractFactory(
'Reverter',
env.l2Wallet
)
})
beforeEach(async () => {
L1SimpleStorage = await Factory__L1SimpleStorage.deploy()
await L1SimpleStorage.deployed()
L2SimpleStorage = await Factory__L2SimpleStorage.deploy()
await L2SimpleStorage.deployed()
L2Reverter = await Factory__L2Reverter.deploy()
await L2Reverter.deployed()
})
describe('L2 => L1', () => {
withdrawalTest(
'should be able to perform a withdrawal from L2 -> L1',
async () => {
const value = `0x${'77'.repeat(32)}`
// Send L2 -> L1 message.
const transaction = await env.messenger.sendMessage(
{
direction: MessageDirection.L2_TO_L1,
target: L1SimpleStorage.address,
message: L1SimpleStorage.interface.encodeFunctionData('setValue', [
value,
]),
},
{
overrides: {
gasLimit: DEFAULT_TEST_GAS_L2,
},
}
)
await env.messenger.waitForMessageStatus(
transaction,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(transaction)
await env.messenger.waitForMessageReceipt(transaction)
expect(await L1SimpleStorage.msgSender()).to.equal(
env.messenger.contracts.l1.L1CrossDomainMessenger.address
)
expect(await L1SimpleStorage.xDomainSender()).to.equal(
await env.messenger.l2Signer.getAddress()
)
expect(await L1SimpleStorage.value()).to.equal(value)
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(1)
}
)
})
describe('L1 => L2', () => {
it('should deposit from L1 -> L2', async () => {
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
const transaction = await env.messenger.sendMessage(
{
direction: MessageDirection.L1_TO_L2,
target: L2SimpleStorage.address,
message: L2SimpleStorage.interface.encodeFunctionData('setValue', [
value,
]),
},
{
l2GasLimit: 5000000,
overrides: {
gasLimit: DEFAULT_TEST_GAS_L1,
},
}
)
const receipt = await env.messenger.waitForMessageReceipt(transaction)
expect(receipt.transactionReceipt.status).to.equal(1)
expect(await L2SimpleStorage.msgSender()).to.equal(
env.messenger.contracts.l2.L2CrossDomainMessenger.address
)
expect(await L2SimpleStorage.txOrigin()).to.equal(
applyL1ToL2Alias(
env.messenger.contracts.l1.L1CrossDomainMessenger.address
)
)
expect(await L2SimpleStorage.xDomainSender()).to.equal(
await env.messenger.l1Signer.getAddress()
)
expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
it('should deposit from L1 -> L2 directly via enqueue', async function () {
this.timeout(
envConfig.MOCHA_TIMEOUT * 2 +
envConfig.DTL_ENQUEUE_CONFIRMATIONS * 15000
)
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
const tx =
await env.messenger.contracts.l1.CanonicalTransactionChain.connect(
env.messenger.l1Signer
).enqueue(
L2SimpleStorage.address,
5000000,
L2SimpleStorage.interface.encodeFunctionData('setValueNotXDomain', [
value,
]),
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
const receipt = await tx.wait()
const waitUntilBlock =
receipt.blockNumber + envConfig.DTL_ENQUEUE_CONFIRMATIONS
let currBlock = await env.messenger.l1Provider.getBlockNumber()
while (currBlock <= waitUntilBlock) {
const progress =
envConfig.DTL_ENQUEUE_CONFIRMATIONS - (waitUntilBlock - currBlock)
console.log(
`Waiting for ${progress}/${envConfig.DTL_ENQUEUE_CONFIRMATIONS} confirmations.`
)
await sleep(5000)
currBlock = await env.messenger.l1Provider.getBlockNumber()
}
console.log('Enqueue should be confirmed.')
await awaitCondition(
async () => {
const sender = await L2SimpleStorage.msgSender()
return sender === (await env.messenger.l1Signer.getAddress())
},
2000,
60
)
// No aliasing when an EOA goes directly to L2.
expect(await L2SimpleStorage.msgSender()).to.equal(
await env.messenger.l1Signer.getAddress()
)
expect(await L2SimpleStorage.txOrigin()).to.equal(
await env.messenger.l1Signer.getAddress()
)
expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
})
})
/* Imports: External */
import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import { MessageStatus } from '@eth-optimism/sdk'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import { withdrawalTest } from './shared/utils'
describe('Bridged tokens', () => {
let env: OptimismEnv
before(async () => {
env = await OptimismEnv.new()
})
let otherWalletL1: Wallet
let otherWalletL2: Wallet
before(async () => {
const other = Wallet.createRandom()
otherWalletL1 = other.connect(env.l1Wallet.provider)
otherWalletL2 = other.connect(env.l2Wallet.provider)
const tx1 = await env.l1Wallet.sendTransaction({
to: otherWalletL1.address,
value: utils.parseEther('0.01'),
})
await tx1.wait()
const tx2 = await env.l2Wallet.sendTransaction({
to: otherWalletL2.address,
value: utils.parseEther('0.01'),
})
await tx2.wait()
})
let L1Factory__ERC20: ContractFactory
let L2Factory__ERC20: ContractFactory
before(async () => {
L1Factory__ERC20 = await ethers.getContractFactory('ERC20', env.l1Wallet)
L2Factory__ERC20 = getContractFactory('L2StandardERC20', env.l2Wallet)
})
// This is one of the only stateful integration tests in which we don't set up a new contract
// before each test. We do this because the test is more of an "actor-based" test where we're
// going through a series of actions and confirming that the actions are performed correctly at
// every step.
let L1__ERC20: Contract
let L2__ERC20: Contract
before(async () => {
// Deploy the L1 ERC20
L1__ERC20 = await L1Factory__ERC20.deploy(1000000, 'OVM Test', 8, 'OVM')
await L1__ERC20.deployed()
// Deploy the L2 ERC20
L2__ERC20 = await L2Factory__ERC20.deploy(
'0x4200000000000000000000000000000000000010',
L1__ERC20.address,
'OVM Test',
'OVM'
)
await L2__ERC20.deployed()
// Approve the L1 ERC20 to spend our money
const tx = await L1__ERC20.approve(
env.messenger.contracts.l1.L1StandardBridge.address,
1000000
)
await tx.wait()
})
it('should deposit tokens into L2', async () => {
await env.messenger.waitForMessageReceipt(
await env.messenger.depositERC20(
L1__ERC20.address,
L2__ERC20.address,
1000
)
)
expect(await L1__ERC20.balanceOf(env.l1Wallet.address)).to.deep.equal(
BigNumber.from(999000)
)
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
BigNumber.from(1000)
)
})
it('should transfer tokens on L2', async () => {
const tx = await L2__ERC20.transfer(otherWalletL1.address, 500)
await tx.wait()
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
BigNumber.from(500)
)
expect(await L2__ERC20.balanceOf(otherWalletL2.address)).to.deep.equal(
BigNumber.from(500)
)
})
withdrawalTest(
'should withdraw tokens from L2 to the depositor',
async () => {
const tx = await env.messenger.withdrawERC20(
L1__ERC20.address,
L2__ERC20.address,
500
)
await env.messenger.waitForMessageStatus(
tx,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(tx)
await env.messenger.waitForMessageReceipt(tx)
expect(await L1__ERC20.balanceOf(env.l1Wallet.address)).to.deep.equal(
BigNumber.from(999500)
)
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
BigNumber.from(0)
)
}
)
withdrawalTest(
'should withdraw tokens from L2 to the transfer recipient',
async () => {
const tx = await env.messenger.withdrawERC20(
L1__ERC20.address,
L2__ERC20.address,
500,
{
signer: otherWalletL2,
}
)
await env.messenger.waitForMessageStatus(
tx,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(tx)
await env.messenger.waitForMessageReceipt(tx)
expect(await L1__ERC20.balanceOf(otherWalletL1.address)).to.deep.equal(
BigNumber.from(500)
)
expect(await L2__ERC20.balanceOf(otherWalletL2.address)).to.deep.equal(
BigNumber.from(0)
)
}
)
// This test demonstrates that an apparent withdrawal bug is in fact non-existent.
// Specifically, the L2 bridge does not check that the L2 token being burned corresponds
// with the L1 token which is specified for the withdrawal.
withdrawalTest(
'should not allow an arbitrary L2 token to be withdrawn in exchange for a legitimate L1 token',
async () => {
// First deposit some of the L1 token to L2, so that there is something which could be stolen.
await env.messenger.waitForMessageReceipt(
await env.messenger.depositERC20(
L1__ERC20.address,
L2__ERC20.address,
1000
)
)
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
BigNumber.from(1000)
)
// Deploy a Fake L2 token, which:
// - returns the address of a legitimate L1 token from its l1Token() getter.
// - allows the L2 bridge to call its burn() function.
const fakeToken = await (
await ethers.getContractFactory('FakeL2StandardERC20', env.l2Wallet)
).deploy(
L1__ERC20.address,
env.messenger.contracts.l2.L2StandardBridge.address
)
await fakeToken.deployed()
const balBefore = await L1__ERC20.balanceOf(otherWalletL1.address)
// Withdraw some of the Fake L2 token, hoping to receive the same amount of the legitimate
// token on L1.
const withdrawalTx = await env.messenger.withdrawERC20(
L1__ERC20.address,
fakeToken.address,
500,
{
signer: otherWalletL2,
}
)
await env.messenger.waitForMessageStatus(
withdrawalTx,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(withdrawalTx)
await env.messenger.waitForMessageReceipt(withdrawalTx)
// Ensure that the L1 recipient address has not received any additional L1 token balance.
expect(await L1__ERC20.balanceOf(otherWalletL1.address)).to.deep.equal(
balBefore
)
}
)
})
/* Imports: External */
import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import { UniswapV3Deployer } from 'uniswap-v3-deploy-plugin/dist/deployer/UniswapV3Deployer'
import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { abi as NFTABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { abi as RouterABI } from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
// Below methods taken from the Uniswap test suite, see
// https://github.com/Uniswap/v3-periphery/blob/main/test/shared/ticks.ts
const getMinTick = (tickSpacing: number) =>
Math.ceil(-887272 / tickSpacing) * tickSpacing
const getMaxTick = (tickSpacing: number) =>
Math.floor(887272 / tickSpacing) * tickSpacing
describe('Contract interactions', () => {
let env: OptimismEnv
let Factory__ERC20: ContractFactory
let otherWallet: Wallet
before(async () => {
env = await OptimismEnv.new()
Factory__ERC20 = await ethers.getContractFactory('ERC20', env.l2Wallet)
otherWallet = Wallet.createRandom().connect(env.l2Wallet.provider)
await env.l2Wallet.sendTransaction({
to: otherWallet.address,
value: utils.parseEther('0.1'),
})
})
describe('ERC20s', () => {
let contract: Contract
before(async () => {
Factory__ERC20 = await ethers.getContractFactory('ERC20', env.l2Wallet)
})
it('should successfully deploy the contract', async () => {
contract = await Factory__ERC20.deploy(100000000, 'OVM Test', 8, 'OVM')
await contract.deployed()
})
it('should support approvals', async () => {
const spender = '0x' + '22'.repeat(20)
const tx = await contract.approve(spender, 1000)
await tx.wait()
let allowance = await contract.allowance(env.l2Wallet.address, spender)
expect(allowance).to.deep.equal(BigNumber.from(1000))
allowance = await contract.allowance(otherWallet.address, spender)
expect(allowance).to.deep.equal(BigNumber.from(0))
const logs = await contract.queryFilter(
contract.filters.Approval(env.l2Wallet.address),
1,
'latest'
)
expect(logs[0].args._owner).to.equal(env.l2Wallet.address)
expect(logs[0].args._spender).to.equal(spender)
expect(logs[0].args._value).to.deep.equal(BigNumber.from(1000))
})
it('should support transferring balances', async () => {
const tx = await contract.transfer(otherWallet.address, 1000)
await tx.wait()
const balFrom = await contract.balanceOf(env.l2Wallet.address)
const balTo = await contract.balanceOf(otherWallet.address)
expect(balFrom).to.deep.equal(BigNumber.from(100000000).sub(1000))
expect(balTo).to.deep.equal(BigNumber.from(1000))
const logs = await contract.queryFilter(
contract.filters.Transfer(env.l2Wallet.address),
1,
'latest'
)
expect(logs[0].args._from).to.equal(env.l2Wallet.address)
expect(logs[0].args._to).to.equal(otherWallet.address)
expect(logs[0].args._value).to.deep.equal(BigNumber.from(1000))
})
it('should support being self destructed', async () => {
const tx = await contract.destroy()
await tx.wait()
const code = await env.l2Wallet.provider.getCode(contract.address)
expect(code).to.equal('0x')
})
})
describe('uniswap', () => {
let contracts: { [name: string]: Contract }
let tokens: Contract[]
before(async () => {
if (
process.env.UNISWAP_POSITION_MANAGER_ADDRESS &&
process.env.UNISWAP_ROUTER_ADDRESS
) {
console.log('Using predeployed Uniswap. Addresses:')
console.log(
`Position manager: ${process.env.UNISWAP_POSITION_MANAGER_ADDRESS}`
)
console.log(`Router: ${process.env.UNISWAP_ROUTER_ADDRESS}`)
contracts = {
positionManager: new Contract(
process.env.UNISWAP_POSITION_MANAGER_ADDRESS,
NFTABI
).connect(env.l2Wallet),
router: new Contract(
process.env.UNISWAP_ROUTER_ADDRESS,
RouterABI
).connect(env.l2Wallet),
}
}
const tokenA = await Factory__ERC20.deploy(100000000, 'OVM1', 8, 'OVM1')
await tokenA.deployed()
const tokenB = await Factory__ERC20.deploy(100000000, 'OVM2', 8, 'OVM2')
await tokenB.deployed()
tokens = [tokenA, tokenB]
tokens.sort((a, b) => {
if (a.address > b.address) {
return 1
}
if (a.address < b.address) {
return -1
}
return 0
})
const tx = await tokens[0].transfer(otherWallet.address, 100000)
await tx.wait()
})
it('should deploy the Uniswap ecosystem', async function () {
if (contracts) {
console.log(
'Skipping Uniswap deployment since addresses are already defined.'
)
this.skip()
return
}
contracts = await UniswapV3Deployer.deploy(env.l2Wallet)
})
it('should deploy and initialize a liquidity pool', async () => {
const tx =
await contracts.positionManager.createAndInitializePoolIfNecessary(
tokens[0].address,
tokens[1].address,
FeeAmount.MEDIUM,
// initial ratio of 1/1
BigNumber.from('79228162514264337593543950336')
)
await tx.wait()
})
it('should approve the contracts', async () => {
for (const wallet of [env.l2Wallet, otherWallet]) {
for (const token of tokens) {
let tx = await token
.connect(wallet)
.approve(contracts.positionManager.address, 100000000)
await tx.wait()
tx = await token
.connect(wallet)
.approve(contracts.router.address, 100000000)
await tx.wait()
}
}
})
it('should mint new positions', async () => {
const tx = await contracts.positionManager.mint(
{
token0: tokens[0].address,
token1: tokens[1].address,
tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
fee: FeeAmount.MEDIUM,
recipient: env.l2Wallet.address,
amount0Desired: 15,
amount1Desired: 15,
amount0Min: 0,
amount1Min: 0,
deadline: Date.now() * 2,
},
{
gasLimit: 10000000,
}
)
await tx.wait()
expect(
await contracts.positionManager.balanceOf(env.l2Wallet.address)
).to.eq(1)
expect(
await contracts.positionManager.tokenOfOwnerByIndex(
env.l2Wallet.address,
0
)
).to.eq(1)
})
it('should swap', async () => {
const tx = await contracts.router.connect(otherWallet).exactInputSingle(
{
tokenIn: tokens[0].address,
tokenOut: tokens[1].address,
fee: FeeAmount.MEDIUM,
recipient: otherWallet.address,
deadline: Date.now() * 2,
amountIn: 10,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0,
},
{
gasLimit: 10000000,
}
)
await tx.wait()
expect(await tokens[1].balanceOf(otherWallet.address)).to.deep.equal(
BigNumber.from('5')
)
})
})
})
/* Imports: External */
import { BigNumber, utils } from 'ethers'
import { serialize } from '@ethersproject/transactions'
import { predeploys, getContractFactory } from '@eth-optimism/contracts'
/* Imports: Internal */
import { expect } from './shared/setup'
import { hardhatTest, gasPriceOracleWallet } from './shared/utils'
import { OptimismEnv } from './shared/env'
const setPrices = async (env: OptimismEnv, value: number | BigNumber) => {
const gasPrice = await env.messenger.contracts.l2.OVM_GasPriceOracle.connect(
gasPriceOracleWallet
).setGasPrice(value)
await gasPrice.wait()
const baseFee = await env.messenger.contracts.l2.OVM_GasPriceOracle.connect(
gasPriceOracleWallet
).setL1BaseFee(value)
await baseFee.wait()
}
describe('Fee Payment Integration Tests', async () => {
let env: OptimismEnv
const other = '0x1234123412341234123412341234123412341234'
before(async () => {
env = await OptimismEnv.new()
})
hardhatTest(
`should return eth_gasPrice equal to OVM_GasPriceOracle.gasPrice`,
async () => {
const assertGasPrice = async () => {
const gasPrice = await env.l2Wallet.getGasPrice()
const oracleGasPrice =
await env.messenger.contracts.l2.OVM_GasPriceOracle.connect(
gasPriceOracleWallet
).gasPrice()
expect(gasPrice).to.deep.equal(oracleGasPrice)
}
await assertGasPrice()
// update the gas price
const tx = await env.messenger.contracts.l2.OVM_GasPriceOracle.connect(
gasPriceOracleWallet
).setGasPrice(1000)
await tx.wait()
await assertGasPrice()
}
)
hardhatTest('Paying a nonzero but acceptable gasPrice fee', async () => {
await setPrices(env, 1000)
const amount = utils.parseEther('0.0000001')
const balanceBefore = await env.l2Wallet.getBalance()
const feeVaultBalanceBefore = await env.l2Wallet.provider.getBalance(
env.messenger.contracts.l2.OVM_SequencerFeeVault.address
)
expect(balanceBefore.gt(amount))
const unsigned = await env.l2Wallet.populateTransaction({
to: other,
value: amount,
gasLimit: 500000,
})
const raw = serialize({
nonce: parseInt(unsigned.nonce.toString(10), 10),
value: unsigned.value,
gasPrice: unsigned.gasPrice,
gasLimit: unsigned.gasLimit,
to: unsigned.to,
data: unsigned.data,
})
const l1Fee = await env.messenger.contracts.l2.OVM_GasPriceOracle.connect(
gasPriceOracleWallet
).getL1Fee(raw)
const tx = await env.l2Wallet.sendTransaction(unsigned)
const receipt = await tx.wait()
expect(receipt.status).to.eq(1)
const balanceAfter = await env.l2Wallet.getBalance()
const feeVaultBalanceAfter = await env.l2Wallet.provider.getBalance(
env.messenger.contracts.l2.OVM_SequencerFeeVault.address
)
const l2Fee = receipt.gasUsed.mul(tx.gasPrice)
const expectedFeePaid = l1Fee.add(l2Fee)
expect(balanceBefore.sub(balanceAfter)).to.deep.equal(
expectedFeePaid.add(amount)
)
// Make sure the fee was transferred to the vault.
expect(feeVaultBalanceAfter.sub(feeVaultBalanceBefore)).to.deep.equal(
expectedFeePaid
)
await setPrices(env, 1)
})
hardhatTest('should compute correct fee', async () => {
await setPrices(env, 1000)
const preBalance = await env.l2Wallet.getBalance()
const OVM_GasPriceOracle = getContractFactory('OVM_GasPriceOracle')
.attach(predeploys.OVM_GasPriceOracle)
.connect(env.l2Wallet)
const WETH = getContractFactory('OVM_ETH')
.attach(predeploys.OVM_ETH)
.connect(env.l2Wallet)
const feeVaultBefore = await WETH.balanceOf(
predeploys.OVM_SequencerFeeVault
)
const unsigned = await env.l2Wallet.populateTransaction({
to: env.l2Wallet.address,
value: 0,
})
const raw = serialize({
nonce: parseInt(unsigned.nonce.toString(10), 10),
value: unsigned.value,
gasPrice: unsigned.gasPrice,
gasLimit: unsigned.gasLimit,
to: unsigned.to,
data: unsigned.data,
})
const l1Fee = await OVM_GasPriceOracle.connect(
gasPriceOracleWallet
).getL1Fee(raw)
const tx = await env.l2Wallet.sendTransaction(unsigned)
const receipt = await tx.wait()
const l2Fee = receipt.gasUsed.mul(tx.gasPrice)
const postBalance = await env.l2Wallet.getBalance()
const feeVaultAfter = await WETH.balanceOf(predeploys.OVM_SequencerFeeVault)
const fee = l1Fee.add(l2Fee)
const balanceDiff = preBalance.sub(postBalance)
const feeReceived = feeVaultAfter.sub(feeVaultBefore)
expect(balanceDiff).to.deep.equal(fee)
// There is no inflation
expect(feeReceived).to.deep.equal(balanceDiff)
await setPrices(env, 1)
})
it('should not be able to withdraw fees before the minimum is met', async () => {
await expect(env.messenger.contracts.l2.OVM_SequencerFeeVault.withdraw()).to
.be.rejected
})
hardhatTest(
'should be able to withdraw fees back to L1 once the minimum is met',
async () => {
const l1FeeWallet =
await env.messenger.contracts.l2.OVM_SequencerFeeVault.l1FeeWallet()
const balanceBefore = await env.l1Wallet.provider.getBalance(l1FeeWallet)
const withdrawalAmount =
await env.messenger.contracts.l2.OVM_SequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
// Transfer the minimum required to withdraw.
const tx = await env.l2Wallet.sendTransaction({
to: env.messenger.contracts.l2.OVM_SequencerFeeVault.address,
value: withdrawalAmount,
gasLimit: 500000,
})
await tx.wait()
const vaultBalance = await env.messenger.contracts.l2.OVM_ETH.balanceOf(
env.messenger.contracts.l2.OVM_SequencerFeeVault.address
)
const withdrawTx =
await env.messenger.contracts.l2.OVM_SequencerFeeVault.withdraw()
// Wait for the withdrawal to be relayed to L1.
await withdrawTx.wait()
await env.relayXDomainMessages(withdrawTx)
await env.waitForXDomainTransaction(withdrawTx)
// Balance difference should be equal to old L2 balance.
const balanceAfter = await env.l1Wallet.provider.getBalance(l1FeeWallet)
expect(balanceAfter.sub(balanceBefore)).to.deep.equal(
BigNumber.from(vaultBalance)
)
}
)
})
/* Imports: External */
import { Contract, BigNumber } from 'ethers'
import { ethers } from 'hardhat'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
export const traceToGasByOpcode = (structLogs, opcode) => {
let gas = 0
const opcodes = []
for (const log of structLogs) {
if (log.op === opcode) {
opcodes.push(opcode)
gas += log.gasCost
}
}
return gas
}
describe('Hard forks', () => {
let env: OptimismEnv
let SimpleStorage: Contract
let SelfDestruction: Contract
let Precompiles: Contract
before(async () => {
env = await OptimismEnv.new()
const Factory__SimpleStorage = await ethers.getContractFactory(
'SimpleStorage',
env.l2Wallet
)
SimpleStorage = await Factory__SimpleStorage.deploy()
const Factory__SelfDestruction = await ethers.getContractFactory(
'SelfDestruction',
env.l2Wallet
)
SelfDestruction = await Factory__SelfDestruction.deploy()
const Factory__Precompiles = await ethers.getContractFactory(
'Precompiles',
env.l2Wallet
)
Precompiles = await Factory__Precompiles.deploy()
})
describe('Berlin', () => {
// https://eips.ethereum.org/EIPS/eip-2929
describe('EIP-2929', () => {
it('should update the gas schedule', async () => {
// Get the tip height
const tip = await env.l2Provider.getBlock('latest')
// send a transaction to be able to trace
const tx = await SimpleStorage.setValueNotXDomain(
`0x${'77'.repeat(32)}`
)
await tx.wait()
// Collect the traces
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
expect(berlinTrace.gas).to.not.eq(preBerlinTrace.gas)
const berlinSstoreCosts = traceToGasByOpcode(
berlinTrace.structLogs,
'SSTORE'
)
const preBerlinSstoreCosts = traceToGasByOpcode(
preBerlinTrace.structLogs,
'SSTORE'
)
expect(berlinSstoreCosts).to.not.eq(preBerlinSstoreCosts)
})
})
// https://eips.ethereum.org/EIPS/eip-2565
describe('EIP-2565', async () => {
it('should become cheaper', async () => {
const tip = await env.l2Provider.getBlock('latest')
const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas)
})
})
})
// Optimism includes EIP-3529 as part of its Berlin hardfork. It is part
// of the London hardfork on L1. Since it is coupled to the Berlin
// hardfork, some of its functionality cannot be directly tests via
// integration tests since we can currently only turn on all of the Berlin
// EIPs or none of the Berlin EIPs
describe('Berlin Additional (L1 London)', () => {
// https://eips.ethereum.org/EIPS/eip-3529
describe('EIP-3529', async () => {
const bytes32Zero = '0x' + '00'.repeat(32)
const bytes32NonZero = '0x' + 'ff'.repeat(32)
it('should lower the refund for storage clear', async () => {
const tip = await env.l2Provider.getBlock('latest')
const value = await SelfDestruction.callStatic.data()
// It should be non zero
expect(BigNumber.from(value).toNumber()).to.not.eq(0)
{
// Set the value to another non zero value
// Going from non zero to non zero
const tx = await SelfDestruction.setData(bytes32NonZero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// Updating a non zero value to another non zero value should not change
expect(berlinTrace.gas).to.deep.eq(preBerlinTrace.gas)
}
{
// Set the value to the zero value
// Going from non zero to zero
const tx = await SelfDestruction.setData(bytes32Zero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// Updating to a zero value from a non zero value should becomes
// more expensive due to this change being coupled with EIP-2929
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
}
{
// Set the value to a non zero value
// Going from zero to non zero
const tx = await SelfDestruction.setData(bytes32NonZero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// Updating to a zero value from a non zero value should becomes
// more expensive due to this change being coupled with EIP-2929
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
}
})
it('should remove the refund for selfdestruct', async () => {
const tip = await env.l2Provider.getBlock('latest')
// Send transaction with a large gas limit
const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// The berlin execution should use more gas than the pre Berlin
// execution because there is no longer a selfdestruct gas
// refund
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
})
})
})
})
import fetch from 'node-fetch'
import { expect } from './shared/setup'
import { envConfig } from './shared/utils'
describe('Healthcheck Tests', () => {
before(async function () {
if (!envConfig.RUN_HEALTHCHECK_TESTS) {
this.skip()
}
})
// Super simple test, is the metric server up?
it('should have metrics exposed', async () => {
const response = await fetch(envConfig.HEALTHCHECK_URL)
expect(response.status).to.equal(200)
})
})
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* Imports: External */
import { ethers } from 'hardhat'
import { expectApprox } from '@eth-optimism/core-utils'
import { predeploys } from '@eth-optimism/contracts'
import { Contract, BigNumber } from 'ethers'
/* Imports: Internal */
import { expect } from './shared/setup'
import { envConfig, DEFAULT_TEST_GAS_L1 } from './shared/utils'
import { OptimismEnv } from './shared/env'
/**
* 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 env: OptimismEnv
before(async () => {
env = await OptimismEnv.new()
})
let Multicall: Contract
let OVMContextStorage: Contract
beforeEach(async () => {
const OVMContextStorageFactory = await ethers.getContractFactory(
'OVMContextStorage',
env.l2Wallet
)
const MulticallFactory = await ethers.getContractFactory(
'Multicall',
env.l2Wallet
)
OVMContextStorage = await OVMContextStorageFactory.deploy()
await OVMContextStorage.deployTransaction.wait()
Multicall = await MulticallFactory.deploy()
await Multicall.deployTransaction.wait()
})
const numTxs = envConfig.OVMCONTEXT_SPEC_NUM_TXS
it('enqueue: L1 contextual values are correctly set in L2', async () => {
for (let i = 0; i < numTxs; i++) {
// Send a transaction from L1 to L2. This will automatically update the L1 contextual
// information like the L1 block number and L1 timestamp.
const tx =
await env.messenger.contracts.l1.L1CrossDomainMessenger.sendMessage(
OVMContextStorage.address,
'0x',
2_000_000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
// Wait for the transaction to be sent over to L2.
await tx.wait()
const pair = await env.waitForXDomainTransaction(tx)
// Get the L1 block that the enqueue transaction was in so that
// the timestamp can be compared against the layer two contract
const l1Block = await env.l1Provider.getBlock(pair.receipt.blockNumber)
const l2Block = await env.l2Provider.getBlock(
pair.remoteReceipt.blockNumber
)
// block.number should return the value of the L2 block number.
const l2BlockNumber = await OVMContextStorage.blockNumbers(i)
expect(l2BlockNumber.toNumber()).to.deep.equal(l2Block.number)
// L1BLOCKNUMBER opcode should return the value of the L1 block number.
const l1BlockNumber = await OVMContextStorage.l1BlockNumbers(i)
expect(l1BlockNumber.toNumber()).to.deep.equal(l1Block.number)
// L1 and L2 blocks will have approximately the same timestamp.
const timestamp = await OVMContextStorage.timestamps(i)
expectApprox(timestamp.toNumber(), l1Block.timestamp, {
percentUpperDeviation: 5,
})
expect(timestamp.toNumber()).to.deep.equal(l2Block.timestamp)
// Difficulty should always be zero.
const difficulty = await OVMContextStorage.difficulty(i)
expect(difficulty.toNumber()).to.equal(0)
// Coinbase should always be sequencer fee vault.
const coinbase = await OVMContextStorage.coinbases(i)
expect(coinbase).to.equal(predeploys.OVM_SequencerFeeVault)
}
})
it('should set correct OVM Context for `eth_call`', async () => {
for (let i = 0; i < numTxs; i++) {
// Make an empty transaction to bump the latest block number.
const dummyTx = await env.l2Wallet.sendTransaction({
to: `0x${'11'.repeat(20)}`,
data: '0x',
})
await dummyTx.wait()
const block = await env.l2Provider.getBlockWithTransactions('latest')
const [, returnData] = await Multicall.callStatic.aggregate(
[
[
OVMContextStorage.address,
OVMContextStorage.interface.encodeFunctionData(
'getCurrentBlockTimestamp'
),
],
[
OVMContextStorage.address,
OVMContextStorage.interface.encodeFunctionData(
'getCurrentBlockNumber'
),
],
[
OVMContextStorage.address,
OVMContextStorage.interface.encodeFunctionData(
'getCurrentL1BlockNumber'
),
],
],
{ blockTag: block.number }
)
const timestamp = BigNumber.from(returnData[0])
const blockNumber = BigNumber.from(returnData[1])
const l1BlockNumber = BigNumber.from(returnData[2])
const tx = block.transactions[0] as any
expect(tx.l1BlockNumber).to.deep.equal(l1BlockNumber.toNumber())
expect(block.timestamp).to.deep.equal(timestamp.toNumber())
expect(block.number).to.deep.equal(blockNumber.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.
*/
// TODO: This test is not reliable. If we really care about this then we need to figure out a
// more reliable way to test this behavior.
it.skip('should return same timestamp and blocknumbers between `eth_call` and `rollup_getInfo`', async () => {
// // As atomically as possible, call `rollup_getInfo` and Multicall 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', []),
// Multicall.callStatic.aggregate([
// [
// OVMContextStorage.address,
// OVMContextStorage.interface.encodeFunctionData(
// 'getCurrentBlockTimestamp'
// ),
// ],
// [
// OVMContextStorage.address,
// OVMContextStorage.interface.encodeFunctionData(
// 'getCurrentL1BlockNumber'
// ),
// ],
// ]),
// ])
// const timestamp = BigNumber.from(returnData[0])
// const blockNumber = BigNumber.from(returnData[1])
// expect(info.ethContext.blockNumber).to.deep.equal(blockNumber.toNumber())
// expect(info.ethContext.timestamp).to.deep.equal(timestamp.toNumber())
})
})
/* Imports: External */
import { ethers } from 'ethers'
import { predeploys, getContractInterface } from '@eth-optimism/contracts'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
describe('predeploys', () => {
let env: OptimismEnv
before(async () => {
env = await OptimismEnv.new()
})
describe('WETH9', () => {
let weth9: ethers.Contract
before(() => {
weth9 = new ethers.Contract(
predeploys.WETH9,
getContractInterface('WETH9'),
env.l2Wallet
)
})
it('should have the correct name', async () => {
expect(await weth9.name()).to.equal('Wrapped Ether')
})
it('should have the correct symbol', async () => {
expect(await weth9.symbol()).to.equal('WETH')
})
it('should have the correct decimals', async () => {
expect(await weth9.decimals()).to.equal(18)
})
})
describe('OVM_ETH', () => {
let ovmEth: ethers.Contract
before(() => {
ovmEth = new ethers.Contract(
predeploys.OVM_ETH,
getContractInterface('OVM_ETH'),
env.l2Wallet
)
})
it('should have the correct name', async () => {
expect(await ovmEth.name()).to.equal('Ether')
})
it('should have the correct symbol', async () => {
expect(await ovmEth.symbol()).to.equal('ETH')
})
it('should have the correct decimals', async () => {
expect(await ovmEth.decimals()).to.equal(18)
})
})
describe('L2CrossDomainMessenger', () => {
let l2CrossDomainMessenger: ethers.Contract
before(() => {
l2CrossDomainMessenger = new ethers.Contract(
predeploys.L2CrossDomainMessenger,
getContractInterface('L2CrossDomainMessenger'),
env.l2Wallet
)
})
it('should throw when calling xDomainMessageSender', async () => {
await expect(
l2CrossDomainMessenger.xDomainMessageSender()
).to.be.revertedWith('xDomainMessageSender is not set')
})
})
describe('L2StandardBridge', () => {
let l2StandardBridge: ethers.Contract
before(() => {
l2StandardBridge = new ethers.Contract(
predeploys.L2StandardBridge,
getContractInterface('L2StandardBridge'),
env.l2Wallet
)
})
it('should have the correct messenger address', async () => {
expect(await l2StandardBridge.messenger()).to.equal(
predeploys.L2CrossDomainMessenger
)
})
it('should have a nonzero bridge address', async () => {
expect(await l2StandardBridge.l1TokenBridge()).to.not.equal(
ethers.constants.AddressZero
)
})
})
describe('OVM_SequencerFeeVault', () => {
let ovmSequencerFeeVault: ethers.Contract
before(() => {
ovmSequencerFeeVault = new ethers.Contract(
predeploys.OVM_SequencerFeeVault,
getContractInterface('OVM_SequencerFeeVault'),
env.l2Wallet
)
})
it('should have a nonzero l1FeeWallet', async () => {
expect(await ovmSequencerFeeVault.l1FeeWallet()).to.not.equal(
ethers.constants.AddressZero
)
})
})
})
/* Imports: Internal */
import { providers } from 'ethers'
import { applyL1ToL2Alias } from '@eth-optimism/core-utils'
import { asL2Provider } from '@eth-optimism/sdk'
import { getContractInterface } from '@eth-optimism/contracts'
/* Imports: External */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import { DEFAULT_TEST_GAS_L1, envConfig } from './shared/utils'
describe('Queue Ingestion', () => {
let env: OptimismEnv
let l2Provider: providers.JsonRpcProvider
before(async () => {
env = await OptimismEnv.new()
l2Provider = asL2Provider(env.l2Wallet.provider as any)
})
// The batch submitter will notice that there are transactions
// that are in the queue and submit them. L2 will pick up the
// sequencer batch appended event and play the transactions.
it('should order transactions correctly', async () => {
const numTxs = envConfig.OVMCONTEXT_SPEC_NUM_TXS
// Enqueue some transactions by building the calldata and then sending
// the transaction to Layer 1
const txs = []
for (let i = 0; i < numTxs; i++) {
const tx =
await env.messenger.contracts.l1.L1CrossDomainMessenger.sendMessage(
`0x${`${i}`.repeat(40)}`,
`0x0${i}`,
1_000_000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
await tx.wait()
txs.push(tx)
}
for (let i = 0; i < numTxs; i++) {
const l1Tx = txs[i]
const l1TxReceipt = await txs[i].wait()
const receipt = await env.waitForXDomainTransaction(l1Tx)
const l2Tx = (await l2Provider.getTransaction(
receipt.remoteTx.hash
)) as any
const params = getContractInterface(
'L2CrossDomainMessenger'
).decodeFunctionData('relayMessage', l2Tx.data)
expect(params._sender.toLowerCase()).to.equal(
env.l1Wallet.address.toLowerCase()
)
expect(params._target).to.equal('0x' + `${i}`.repeat(40))
expect(l2Tx.queueOrigin).to.equal('l1')
expect(l2Tx.l1TxOrigin.toLowerCase()).to.equal(
applyL1ToL2Alias(
env.messenger.contracts.l1.L1CrossDomainMessenger.address
).toLowerCase()
)
expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber)
}
})
})
/* Imports: External */
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { sleep } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import {
defaultTransactionFactory,
gasPriceForL2,
envConfig,
} from './shared/utils'
describe('Replica Tests', () => {
let env: OptimismEnv
before(async function () {
if (!envConfig.RUN_REPLICA_TESTS) {
this.skip()
return
}
env = await OptimismEnv.new()
})
describe('Matching blocks', () => {
it('should sync a transaction', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2()
const result = await env.l2Wallet.sendTransaction(tx)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.replicaProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const replicaBlock = (await env.replicaProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash)
})
it('sync an unprotected tx (eip155)', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.l2Provider.sendTransaction(signed)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.replicaProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const replicaBlock = (await env.replicaProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash)
})
it('should forward tx to sequencer', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(),
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.replicaProvider.sendTransaction(signed)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.replicaProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const replicaBlock = (await env.replicaProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash)
})
})
})
This diff is collapsed.
/* Imports: External */
import { utils, Wallet, providers, Transaction } from 'ethers'
import {
TransactionResponse,
TransactionReceipt,
} from '@ethersproject/providers'
import { getChainId, sleep } from '@eth-optimism/core-utils'
import {
CrossChainMessenger,
MessageStatus,
MessageDirection,
StandardBridgeAdapter,
ETHBridgeAdapter,
BridgeAdapterData,
} from '@eth-optimism/sdk'
import { predeploys } from '@eth-optimism/contracts'
/* Imports: Internal */
import {
l1Provider,
l2Provider,
replicaProvider,
verifierProvider,
l1Wallet,
l2Wallet,
fundUser,
envConfig,
} from './utils'
export interface CrossDomainMessagePair {
tx: Transaction
receipt: TransactionReceipt
remoteTx: Transaction
remoteReceipt: TransactionReceipt
}
/// Helper class for instantiating a test environment with a funded account
export class OptimismEnv {
// The wallets
l1Wallet: Wallet
l2Wallet: Wallet
// The providers
messenger: CrossChainMessenger
l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider
replicaProvider: providers.JsonRpcProvider
verifierProvider: providers.JsonRpcProvider
constructor(args: any) {
this.l1Wallet = args.l1Wallet
this.l2Wallet = args.l2Wallet
this.messenger = args.messenger
this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider
this.replicaProvider = args.replicaProvider
this.verifierProvider = args.verifierProvider
}
static async new(): Promise<OptimismEnv> {
let bridgeOverrides: BridgeAdapterData
if (envConfig.L1_STANDARD_BRIDGE) {
bridgeOverrides = {
Standard: {
Adapter: StandardBridgeAdapter,
l1Bridge: envConfig.L1_STANDARD_BRIDGE,
l2Bridge: predeploys.L2StandardBridge,
},
ETH: {
Adapter: ETHBridgeAdapter,
l1Bridge: envConfig.L1_STANDARD_BRIDGE,
l2Bridge: predeploys.L2StandardBridge,
},
}
}
const messenger = new CrossChainMessenger({
l1SignerOrProvider: l1Wallet,
l2SignerOrProvider: l2Wallet,
l1ChainId: await getChainId(l1Provider),
l2ChainId: await getChainId(l2Provider),
contracts: {
l1: {
AddressManager: envConfig.ADDRESS_MANAGER,
L1CrossDomainMessenger: envConfig.L1_CROSS_DOMAIN_MESSENGER,
L1StandardBridge: envConfig.L1_STANDARD_BRIDGE,
StateCommitmentChain: envConfig.STATE_COMMITMENT_CHAIN,
CanonicalTransactionChain: envConfig.CANONICAL_TRANSACTION_CHAIN,
BondManager: envConfig.BOND_MANAGER,
},
},
bridges: bridgeOverrides,
})
// fund the user if needed
const balance = await l2Wallet.getBalance()
const min = envConfig.L2_WALLET_MIN_BALANCE_ETH.toString()
const topUp = envConfig.L2_WALLET_TOP_UP_AMOUNT_ETH.toString()
if (balance.lt(utils.parseEther(min))) {
await fundUser(messenger, utils.parseEther(topUp))
}
return new OptimismEnv({
l1Wallet,
l2Wallet,
messenger,
l1Provider,
l2Provider,
verifierProvider,
replicaProvider,
})
}
async waitForXDomainTransaction(
tx: Promise<TransactionResponse> | TransactionResponse
): Promise<CrossDomainMessagePair> {
// await it if needed
tx = await tx
const receipt = await tx.wait()
const resolved = await this.messenger.toCrossChainMessage(tx)
const messageReceipt = await this.messenger.waitForMessageReceipt(tx)
let fullTx: any
let remoteTx: any
if (resolved.direction === MessageDirection.L1_TO_L2) {
fullTx = await this.messenger.l1Provider.getTransaction(tx.hash)
remoteTx = await this.messenger.l2Provider.getTransaction(
messageReceipt.transactionReceipt.transactionHash
)
} else {
fullTx = await this.messenger.l2Provider.getTransaction(tx.hash)
remoteTx = await this.messenger.l1Provider.getTransaction(
messageReceipt.transactionReceipt.transactionHash
)
}
return {
tx: fullTx,
receipt,
remoteTx,
remoteReceipt: messageReceipt.transactionReceipt,
}
}
/**
* Relays all L2 => L1 messages found in a given L2 transaction.
*
* @param tx Transaction to find messages in.
*/
async relayXDomainMessages(
tx: Promise<TransactionResponse> | TransactionResponse
): Promise<void> {
tx = await tx
await tx.wait()
const messages = await this.messenger.getMessagesByTransaction(tx)
if (messages.length === 0) {
return
}
for (const message of messages) {
await this.messenger.waitForMessageStatus(
message,
MessageStatus.READY_FOR_RELAY
)
let relayed = false
while (!relayed) {
try {
await this.messenger.finalizeMessage(message)
relayed = true
} catch (err) {
if (
err.message.includes('Nonce too low') ||
err.message.includes('transaction was replaced') ||
err.message.includes(
'another transaction with same nonce in the queue'
)
) {
// Sometimes happens when we run tests in parallel.
await sleep(5000)
} else if (
err.message.includes('message has already been received')
) {
// Message already relayed, this is fine.
relayed = true
} else {
throw err
}
}
}
await this.messenger.waitForMessageReceipt(message)
}
}
}
/* External Imports */
import chai = require('chai')
import chaiAsPromised from 'chai-as-promised'
import { solidity } from 'ethereum-waffle'
chai.use(solidity)
chai.use(chaiAsPromised)
const expect = chai.expect
export { expect }
/* Imports: External */
import { ethers } from 'ethers'
import { sleep } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { OptimismEnv } from './env'
import { gasPriceForL1, gasPriceForL2 } from './utils'
interface TransactionParams {
contract: ethers.Contract
functionName: string
functionParams: any[]
}
// Arbitrary big amount of gas for the L1<>L2 messages.
const MESSAGE_GAS = 8_000_000
export const fundRandomWallet = async (
env: OptimismEnv,
wallet: ethers.Wallet,
value: ethers.BigNumber
): Promise<ethers.Wallet> => {
const fundTx = await env.l1Wallet.sendTransaction({
gasLimit: 25_000,
to: wallet.address,
gasPrice: await gasPriceForL1(),
value,
})
await fundTx.wait()
return wallet
}
export const executeL1ToL2Transaction = async (
env: OptimismEnv,
wallet: ethers.Wallet,
tx: TransactionParams
) => {
const signer = wallet.connect(env.l1Wallet.provider)
const receipt = await retryOnNonceError(async () =>
env.messenger.contracts.l1.L1CrossDomainMessenger.connect(
signer
).sendMessage(
tx.contract.address,
tx.contract.interface.encodeFunctionData(
tx.functionName,
tx.functionParams
),
MESSAGE_GAS,
{
gasPrice: await gasPriceForL1(),
}
)
)
await env.waitForXDomainTransaction(receipt)
}
export const executeL2ToL1Transaction = async (
env: OptimismEnv,
wallet: ethers.Wallet,
tx: TransactionParams
) => {
const signer = wallet.connect(env.l2Wallet.provider)
const receipt = await retryOnNonceError(() =>
env.messenger.contracts.l2.L2CrossDomainMessenger.connect(
signer
).sendMessage(
tx.contract.address,
tx.contract.interface.encodeFunctionData(
tx.functionName,
tx.functionParams
),
MESSAGE_GAS,
{
gasPrice: gasPriceForL2(),
}
)
)
await env.relayXDomainMessages(receipt)
await env.waitForXDomainTransaction(receipt)
}
export const executeL2Transaction = async (
env: OptimismEnv,
wallet: ethers.Wallet,
tx: TransactionParams
) => {
const signer = wallet.connect(env.l2Wallet.provider)
const result = await retryOnNonceError(() =>
tx.contract
.connect(signer)
.functions[tx.functionName](...tx.functionParams, {
gasPrice: gasPriceForL2(),
})
)
await result.wait()
}
export const executeRepeatedL1ToL2Transactions = async (
env: OptimismEnv,
wallets: ethers.Wallet[],
tx: TransactionParams
) => {
for (const wallet of wallets) {
await executeL1ToL2Transaction(env, wallet, tx)
}
}
export const executeRepeatedL2ToL1Transactions = async (
env: OptimismEnv,
wallets: ethers.Wallet[],
tx: TransactionParams
) => {
for (const wallet of wallets) {
await executeL2ToL1Transaction(env, wallet, tx)
}
}
export const executeRepeatedL2Transactions = async (
env: OptimismEnv,
wallets: ethers.Wallet[],
tx: TransactionParams
) => {
for (const wallet of wallets) {
await executeL2Transaction(env, wallet, tx)
}
}
export const executeL1ToL2TransactionsParallel = async (
env: OptimismEnv,
wallets: ethers.Wallet[],
tx: TransactionParams
) => {
await Promise.all(wallets.map((w) => executeL1ToL2Transaction(env, w, tx)))
}
export const executeL2ToL1TransactionsParallel = async (
env: OptimismEnv,
wallets: ethers.Wallet[],
tx: TransactionParams
) => {
await Promise.all(wallets.map((w) => executeL2ToL1Transaction(env, w, tx)))
}
export const executeL2TransactionsParallel = async (
env: OptimismEnv,
wallets: ethers.Wallet[],
tx: TransactionParams
) => {
await Promise.all(wallets.map((w) => executeL2Transaction(env, w, tx)))
}
const retryOnNonceError = async (cb: () => Promise<any>): Promise<any> => {
while (true) {
try {
return await cb()
} catch (err) {
const msg = err.message.toLowerCase()
if (
msg.includes('nonce too low') ||
msg.includes('nonce has already been used') ||
msg.includes('transaction was replaced') ||
msg.includes('another transaction with same nonce in the queue') ||
msg.includes('reverted without a reason')
) {
console.warn('Retrying transaction after nonce error.')
await sleep(5000)
continue
}
throw err
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import { SequencerBatch, BatchType } from '@eth-optimism/core-utils'
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import { envConfig } from './shared/utils'
describe('Batch Serialization', () => {
let env: OptimismEnv
// Allow for each type to be tested. The env var here must be
// the same value that is passed to the batch submitter
const batchType = envConfig.BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE.toUpperCase()
before(async () => {
env = await OptimismEnv.new()
})
it('should fetch batches', async () => {
const tip = await env.l1Provider.getBlockNumber()
const ctc = env.messenger.contracts.l1.CanonicalTransactionChain
const logs = await ctc.queryFilter(
ctc.filters.TransactionBatchAppended(),
0,
tip
)
// collect all of the batches
const batches = []
for (const log of logs) {
const tx = await env.l1Provider.getTransaction(log.transactionHash)
batches.push(tx.data)
}
expect(batches.length).to.be.gt(0, 'Submit some batches first')
let latest = 0
// decode all of the batches
for (const batch of batches) {
// Typings don't work?
const decoded = (SequencerBatch as any).fromHex(batch)
expect(decoded.type).to.eq(BatchType[batchType])
// Iterate over all of the transactions, fetch them
// by hash and make sure their blocknumbers are in
// ascending order. This lets us skip handling deposits here
for (const transaction of decoded.transactions) {
const tx = transaction.toTransaction()
const got = await env.l2Provider.getTransaction(tx.hash)
expect(got).to.not.eq(null)
expect(got.blockNumber).to.be.gt(latest)
latest = got.blockNumber
}
}
})
})
{
"extends": "../tsconfig.json",
"compilerOptions": {
"resolveJsonModule": true
},
"include": [
"./test",
"sync-tests/*.ts",
"./actor-tests/**/*.ts",
"./artifacts/**/*.json",
"./tasks/**/*.ts",
"./package.json"
],
"files": ["./hardhat.config.ts"]
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment