Commit 6f0ae0a7 authored by Georgios Konstantopoulos's avatar Georgios Konstantopoulos Committed by GitHub

feat: bump dependencies to latest master (#35)

* ci: disable yarn install cache

* chore: use yarn test:ci to only test changes since master

* ci: use separate job for linting contracts

* feat(contracts): update to https://github.com/ethereum-optimism/contracts/commit/06cdfb45223d239b828ca88623717de958c96a69

* feat(hardhat-ovm): replace env var with OVM config network

https://github.com/ethereum-optimism/plugins/pull/35

* feat(batch-submitter): update to https://github.com/ethereum-optimism/batch-submitter/commit/8cd92d49272223ca2aa3d7cd39cb315a2dd1459f

* feat(dtl): update to https://github.com/ethereum-optimism/data-transport-layer/commit/b1e340a32bd6986ba2c381a10229d49c0b274648

More unit tests and slight type refactoring

* chore: regenerate yarn lock

* fix(integration-tests): use `--network optimism` instead of env var

* chore: yarn lint
parent d8a82f47
...@@ -41,8 +41,7 @@ jobs: ...@@ -41,8 +41,7 @@ jobs:
- name: Install integration test dependencies - name: Install integration test dependencies
working-directory: ./integration-tests working-directory: ./integration-tests
# only install dependencies if the cache was invalidated # if: steps.yarn-cache.outputs.cache-hit != 'true'
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install run: yarn install
- name: Build deps for the integration tests - name: Build deps for the integration tests
......
...@@ -15,8 +15,8 @@ on: ...@@ -15,8 +15,8 @@ on:
- 'packages/**/*.ts' - 'packages/**/*.ts'
jobs: jobs:
build-test-lint: test:
name: Run job on ${{matrix.node}} name: Run tests on ${{matrix.node}}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
...@@ -48,14 +48,42 @@ jobs: ...@@ -48,14 +48,42 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
# only install dependencies if there was a change in the deps # only install dependencies if there was a change in the deps
if: steps.yarn-cache.outputs.cache-hit != 'true' # if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install run: yarn install
- name: Build - name: Build
run: yarn build run: yarn build
- name: Lint
run: yarn lint
- name: Test - name: Test
run: yarn test run: yarn test:ci
lint:
name: Linting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Fetch history
run: git fetch
- uses: actions/setup-node@v1
with:
node-version: 14
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install Dependencies
# only install dependencies if there was a change in the deps
# if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install
- name: Lint
run: yarn lint:ci
...@@ -8,6 +8,12 @@ const config: HardhatUserConfig = { ...@@ -8,6 +8,12 @@ const config: HardhatUserConfig = {
mocha: { mocha: {
timeout: 100000, timeout: 100000,
}, },
networks: {
optimism: {
url: 'http://localhost:8545',
ovm: true,
},
},
} }
export default config export default config
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"lint": "yarn lint:fix && yarn lint:check", "lint": "yarn lint:fix && yarn lint:check",
"lint:check": "tslint --format stylish --project .", "lint:check": "tslint --format stylish --project .",
"lint:fix": "prettier --config ./prettier-config.json --write 'test/**/*.ts'", "lint:fix": "prettier --config ./prettier-config.json --write 'test/**/*.ts'",
"test:integration": "TARGET=ovm hardhat test" "test:integration": "hardhat --network optimism test"
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/contracts": "^0.1.11", "@eth-optimism/contracts": "^0.1.11",
......
...@@ -2,9 +2,9 @@ ADDRESS_MANAGER_ADDRESS= ...@@ -2,9 +2,9 @@ ADDRESS_MANAGER_ADDRESS=
DEBUG=info*,error*,warn*,debug* DEBUG=info*,error*,warn*,debug*
MAX_TX_SIZE=90000 MAX_L1_TX_SIZE=90000
MIN_TX_SIZE=0 MIN_L1_TX_SIZE=0
MAX_BATCH_SIZE=50 MAX_TX_BATCH_COUNT=50
POLL_INTERVAL=15000 POLL_INTERVAL=15000
NUM_CONFIRMATIONS=0 NUM_CONFIRMATIONS=0
......
...@@ -13,8 +13,10 @@ ...@@ -13,8 +13,10 @@
"scripts": { "scripts": {
"clean": "yarn lerna run clean", "clean": "yarn lerna run clean",
"build": "yarn lerna run build", "build": "yarn lerna run build",
"test": "yarn lerna run test --parallel --since origin/master", "test": "yarn lerna run test --parallel",
"lint": "yarn lerna run lint --parallel --since origin/master", "test:ci": "yarn lerna run test --parallel --since origin/master",
"lint": "yarn lerna run lint",
"lint:ci": "yarn lerna run lint --parallel --since origin/master",
"lint:fix": "yarn lerna run lint:fix" "lint:fix": "yarn lerna run lint:fix"
} }
} }
build
dist
node_modules
.env
cache/*
# vim
*.swp
{ {
"name": "@eth-optimism/batch-submitter", "name": "@eth-optimism/batch-submitter",
"version": "0.1.5", "version": "0.1.7",
"description": "[Optimism] Batch submission for sequencer & aggregators", "description": "[Optimism] Batch submission for sequencer & aggregators",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
"scripts": { "scripts": {
"start": "node ./exec/run-batch-submitter.js", "start": "node ./exec/run-batch-submitter.js",
"build": "tsc -p ./tsconfig.build.json", "build": "tsc -p ./tsconfig.build.json",
"clean": "rimraf dist/ ./tsconfig.build.tsbuildinfo", "clean": "rimraf cache/ dist/ ./tsconfig.build.tsbuildinfo",
"lint": "yarn lint:fix && yarn lint:check", "lint": "yarn lint:fix && yarn lint:check",
"lint:check": "tslint --format stylish --project .", "lint:check": "tslint --format stylish --project .",
"lint:fix": "prettier --config prettier-config.json --write \"hardhat.config.ts\" \"{src,exec,test}/**/*.ts\"", "lint:fix": "prettier --config prettier-config.json --write \"hardhat.config.ts\" \"{src,exec,test}/**/*.ts\"",
......
...@@ -131,9 +131,10 @@ export abstract class BatchSubmitter { ...@@ -131,9 +131,10 @@ export abstract class BatchSubmitter {
} }
protected _shouldSubmitBatch(batchSizeInBytes: number): boolean { protected _shouldSubmitBatch(batchSizeInBytes: number): boolean {
const currentTimestamp = Date.now()
const isTimeoutReached = const isTimeoutReached =
this.lastBatchSubmissionTimestamp + this.maxBatchSubmissionTime <= this.lastBatchSubmissionTimestamp + this.maxBatchSubmissionTime <=
Date.now() currentTimestamp
if (batchSizeInBytes < this.minTxSize) { if (batchSizeInBytes < this.minTxSize) {
if (!isTimeoutReached) { if (!isTimeoutReached) {
this.log.info( this.log.info(
...@@ -141,12 +142,24 @@ export abstract class BatchSubmitter { ...@@ -141,12 +142,24 @@ export abstract class BatchSubmitter {
{ {
batchSizeInBytes, batchSizeInBytes,
minTxSize: this.minTxSize, minTxSize: this.minTxSize,
lastBatchSubmissionTimestamp: this.lastBatchSubmissionTimestamp,
currentTimestamp,
} }
) )
return false return false
} }
this.log.info('Timeout reached.') this.log.info('Timeout reached, proceeding with batch submission.', {
batchSizeInBytes,
lastBatchSubmissionTimestamp: this.lastBatchSubmissionTimestamp,
currentTimestamp,
})
return true
} }
this.log.info('Sufficient batch size, proceeding with batch submission.', {
batchSizeInBytes,
lastBatchSubmissionTimestamp: this.lastBatchSubmissionTimestamp,
currentTimestamp,
})
return true return true
} }
......
/* External Imports */ /* External Imports */
import { Promise as bPromise } from 'bluebird'
import { Contract, Signer } from 'ethers' import { Contract, Signer } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider' import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { getContractFactory } from '@eth-optimism/contracts' import { getContractFactory } from '@eth-optimism/contracts'
...@@ -108,14 +109,13 @@ export class StateBatchSubmitter extends BatchSubmitter { ...@@ -108,14 +109,13 @@ export class StateBatchSubmitter extends BatchSubmitter {
const startBlock: number = const startBlock: number =
(await this.chainContract.getTotalElements()).toNumber() + BLOCK_OFFSET (await this.chainContract.getTotalElements()).toNumber() + BLOCK_OFFSET
// We will submit state roots for txs which have been in the tx chain for a while. // We will submit state roots for txs which have been in the tx chain for a while.
const callBlockNumber: number =
(await this.signer.provider.getBlockNumber()) - this.finalityConfirmations
const totalElements: number = const totalElements: number =
(await this.ctcContract.getTotalElements()).toNumber() + BLOCK_OFFSET (await this.ctcContract.getTotalElements()).toNumber() + BLOCK_OFFSET
const endBlock: number = Math.min( const endBlock: number = Math.min(
startBlock + this.maxBatchSize, startBlock + this.maxBatchSize,
totalElements totalElements
) )
if (startBlock >= endBlock) { if (startBlock >= endBlock) {
if (startBlock > endBlock) { if (startBlock > endBlock) {
this.log.error( this.log.error(
...@@ -142,7 +142,7 @@ export class StateBatchSubmitter extends BatchSubmitter { ...@@ -142,7 +142,7 @@ export class StateBatchSubmitter extends BatchSubmitter {
'appendStateBatch', 'appendStateBatch',
[batch, startBlock] [batch, startBlock]
) )
if (!this._shouldSubmitBatch(tx.length * 2)) { if (!this._shouldSubmitBatch(tx.length / 2)) {
return return
} }
...@@ -172,21 +172,22 @@ export class StateBatchSubmitter extends BatchSubmitter { ...@@ -172,21 +172,22 @@ export class StateBatchSubmitter extends BatchSubmitter {
startBlock: number, startBlock: number,
endBlock: number endBlock: number
): Promise<Bytes32[]> { ): Promise<Bytes32[]> {
const batch: Bytes32[] = [] const blockRange = endBlock - startBlock
const batch: Bytes32[] = await bPromise.map(
for (let i = startBlock; i < endBlock; i++) { [...Array(blockRange).keys()],
const block = (await this.l2Provider.getBlockWithTransactions( async (i: number) => {
i this.log.debug('Fetching L2BatchElement', { blockNo: startBlock + i })
)) as L2Block const block = (await this.l2Provider.getBlockWithTransactions(
if (block.transactions[0].from === this.fraudSubmissionAddress) { startBlock + i
batch.push( )) as L2Block
'0xbad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1' if (block.transactions[0].from === this.fraudSubmissionAddress) {
) this.fraudSubmissionAddress = 'no fraud'
this.fraudSubmissionAddress = 'no fraud' return '0xbad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1'
} else { }
batch.push(block.stateRoot) return block.stateRoot
} },
} { concurrency: 100 }
)
let tx = this.chainContract.interface.encodeFunctionData( let tx = this.chainContract.interface.encodeFunctionData(
'appendStateBatch', 'appendStateBatch',
...@@ -199,6 +200,7 @@ export class StateBatchSubmitter extends BatchSubmitter { ...@@ -199,6 +200,7 @@ export class StateBatchSubmitter extends BatchSubmitter {
startBlock, startBlock,
]) ])
} }
return batch return batch
} }
} }
...@@ -237,7 +237,12 @@ export class TransactionBatchSubmitter extends BatchSubmitter { ...@@ -237,7 +237,12 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
batchParams, batchParams,
wasBatchTruncated, wasBatchTruncated,
] = await this._generateSequencerBatchParams(startBlock, endBlock) ] = await this._generateSequencerBatchParams(startBlock, endBlock)
const batchSizeInBytes = encodeAppendSequencerBatch(batchParams).length * 2 const batchSizeInBytes = encodeAppendSequencerBatch(batchParams).length / 2
// Only submit batch if one of the following is true:
// 1. it was truncated
// 2. it is large enough
// 3. enough time has passed since last submission
if (!wasBatchTruncated && !this._shouldSubmitBatch(batchSizeInBytes)) { if (!wasBatchTruncated && !this._shouldSubmitBatch(batchSizeInBytes)) {
return return
} }
...@@ -278,7 +283,7 @@ export class TransactionBatchSubmitter extends BatchSubmitter { ...@@ -278,7 +283,7 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
this.log.debug('Fetching L2BatchElement', { blockNo: startBlock + i }) this.log.debug('Fetching L2BatchElement', { blockNo: startBlock + i })
return this._getL2BatchElement(startBlock + i) return this._getL2BatchElement(startBlock + i)
}, },
{ concurrency: 50 } { concurrency: 100 }
) )
// Fix our batches if we are configured to. TODO: Remove this. // Fix our batches if we are configured to. TODO: Remove this.
......
...@@ -32,11 +32,13 @@ interface RequiredEnvVars { ...@@ -32,11 +32,13 @@ interface RequiredEnvVars {
// The layer one address manager address // The layer one address manager address
ADDRESS_MANAGER_ADDRESS: 'ADDRESS_MANAGER_ADDRESS' ADDRESS_MANAGER_ADDRESS: 'ADDRESS_MANAGER_ADDRESS'
// The minimum size in bytes of any L1 transactions generated by the batch submitter. // The minimum size in bytes of any L1 transactions generated by the batch submitter.
MIN_TX_SIZE: 'MIN_TX_SIZE' MIN_L1_TX_SIZE: 'MIN_L1_TX_SIZE'
// The maximum size in bytes of any L1 transactions generated by the batch submitter. // The maximum size in bytes of any L1 transactions generated by the batch submitter.
MAX_TX_SIZE: 'MAX_TX_SIZE' MAX_L1_TX_SIZE: 'MAX_L1_TX_SIZE'
// The maximum number of L2 transactions that can ever be in a batch. // The maximum number of L2 transactions that can ever be in a batch.
MAX_BATCH_SIZE: 'MAX_BATCH_SIZE' MAX_TX_BATCH_COUNT: 'MAX_TX_BATCH_COUNT'
// The maximum number of L2 state roots that can ever be in a batch.
MAX_STATE_BATCH_COUNT: 'MAX_STATE_BATCH_COUNT'
// The maximum amount of time (seconds) that we will wait before submitting an under-sized batch. // The maximum amount of time (seconds) that we will wait before submitting an under-sized batch.
MAX_BATCH_SUBMISSION_TIME: 'MAX_BATCH_SUBMISSION_TIME' MAX_BATCH_SUBMISSION_TIME: 'MAX_BATCH_SUBMISSION_TIME'
// The delay in milliseconds between querying L2 for more transactions / to create a new batch. // The delay in milliseconds between querying L2 for more transactions / to create a new batch.
...@@ -62,9 +64,10 @@ const requiredEnvVars: RequiredEnvVars = { ...@@ -62,9 +64,10 @@ const requiredEnvVars: RequiredEnvVars = {
L1_NODE_WEB3_URL: 'L1_NODE_WEB3_URL', L1_NODE_WEB3_URL: 'L1_NODE_WEB3_URL',
L2_NODE_WEB3_URL: 'L2_NODE_WEB3_URL', L2_NODE_WEB3_URL: 'L2_NODE_WEB3_URL',
ADDRESS_MANAGER_ADDRESS: 'ADDRESS_MANAGER_ADDRESS', ADDRESS_MANAGER_ADDRESS: 'ADDRESS_MANAGER_ADDRESS',
MIN_TX_SIZE: 'MIN_TX_SIZE', MIN_L1_TX_SIZE: 'MIN_L1_TX_SIZE',
MAX_TX_SIZE: 'MAX_TX_SIZE', MAX_L1_TX_SIZE: 'MAX_L1_TX_SIZE',
MAX_BATCH_SIZE: 'MAX_BATCH_SIZE', MAX_TX_BATCH_COUNT: 'MAX_TX_BATCH_COUNT',
MAX_STATE_BATCH_COUNT: 'MAX_STATE_BATCH_COUNT',
MAX_BATCH_SUBMISSION_TIME: 'MAX_BATCH_SUBMISSION_TIME', MAX_BATCH_SUBMISSION_TIME: 'MAX_BATCH_SUBMISSION_TIME',
POLL_INTERVAL: 'POLL_INTERVAL', POLL_INTERVAL: 'POLL_INTERVAL',
NUM_CONFIRMATIONS: 'NUM_CONFIRMATIONS', NUM_CONFIRMATIONS: 'NUM_CONFIRMATIONS',
...@@ -144,9 +147,9 @@ export const run = async () => { ...@@ -144,9 +147,9 @@ export const run = async () => {
const txBatchSubmitter = new TransactionBatchSubmitter( const txBatchSubmitter = new TransactionBatchSubmitter(
sequencerSigner, sequencerSigner,
l2Provider, l2Provider,
parseInt(requiredEnvVars.MIN_TX_SIZE, 10), parseInt(requiredEnvVars.MIN_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_TX_SIZE, 10), parseInt(requiredEnvVars.MAX_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_BATCH_SIZE, 10), parseInt(requiredEnvVars.MAX_TX_BATCH_COUNT, 10),
parseInt(requiredEnvVars.MAX_BATCH_SUBMISSION_TIME, 10) * 1_000, parseInt(requiredEnvVars.MAX_BATCH_SUBMISSION_TIME, 10) * 1_000,
parseInt(requiredEnvVars.NUM_CONFIRMATIONS, 10), parseInt(requiredEnvVars.NUM_CONFIRMATIONS, 10),
parseInt(requiredEnvVars.RESUBMISSION_TIMEOUT, 10) * 1_000, parseInt(requiredEnvVars.RESUBMISSION_TIMEOUT, 10) * 1_000,
...@@ -164,9 +167,9 @@ export const run = async () => { ...@@ -164,9 +167,9 @@ export const run = async () => {
const stateBatchSubmitter = new StateBatchSubmitter( const stateBatchSubmitter = new StateBatchSubmitter(
sequencerSigner, sequencerSigner,
l2Provider, l2Provider,
parseInt(requiredEnvVars.MIN_TX_SIZE, 10), parseInt(requiredEnvVars.MIN_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_TX_SIZE, 10), parseInt(requiredEnvVars.MAX_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_BATCH_SIZE, 10), parseInt(requiredEnvVars.MAX_STATE_BATCH_COUNT, 10),
parseInt(requiredEnvVars.MAX_BATCH_SUBMISSION_TIME, 10) * 1_000, parseInt(requiredEnvVars.MAX_BATCH_SUBMISSION_TIME, 10) * 1_000,
parseInt(requiredEnvVars.NUM_CONFIRMATIONS, 10), parseInt(requiredEnvVars.NUM_CONFIRMATIONS, 10),
parseInt(requiredEnvVars.RESUBMISSION_TIMEOUT, 10) * 1_000, parseInt(requiredEnvVars.RESUBMISSION_TIMEOUT, 10) * 1_000,
......
...@@ -88,6 +88,7 @@ export class MockchainProvider extends OptimismProvider { ...@@ -88,6 +88,7 @@ export class MockchainProvider extends OptimismProvider {
public setL2BlockData( public setL2BlockData(
tx: L2Transaction, tx: L2Transaction,
timestamp?: number, timestamp?: number,
stateRoot?: string,
start: number = 1, start: number = 1,
end: number = this.mockBlocks.length end: number = this.mockBlocks.length
) { ) {
...@@ -99,6 +100,7 @@ export class MockchainProvider extends OptimismProvider { ...@@ -99,6 +100,7 @@ export class MockchainProvider extends OptimismProvider {
...this.mockBlocks[i].transactions[0], ...this.mockBlocks[i].transactions[0],
...tx, ...tx,
} }
this.mockBlocks[i].stateRoot = stateRoot
} }
} }
......
...@@ -274,6 +274,7 @@ describe('TransactionBatchSubmitter', () => { ...@@ -274,6 +274,7 @@ describe('TransactionBatchSubmitter', () => {
l1TxOrigin: null, l1TxOrigin: null,
} as any, } as any,
nextQueueElement.timestamp - 1, nextQueueElement.timestamp - 1,
'', // blank state root
3, 3,
6 6
) )
......
{ {
"extends": "../../tsconfig.json" "extends": "../../tsconfig.json",
"compilerOptions": {
"resolveJsonModule": true
}
} }
module.exports = {
skipFiles: [
'./test-helpers',
'./test-libraries',
'./optimistic-ethereum/mockOVM'
],
mocha: {
grep: "@skip-on-coverage",
invert: true
}
};
...@@ -54,6 +54,16 @@ Run specific tests by giving a path to the file you want to run: ...@@ -54,6 +54,16 @@ Run specific tests by giving a path to the file you want to run:
yarn test ./test/path/to/my/test.spec.ts yarn test ./test/path/to/my/test.spec.ts
``` ```
### Measuring test coverage:
```shell
yarn test-coverage
```
The output is most easily viewable by opening the html file in your browser:
```shell
open ./coverage/index.html
```
### Compiling and Building ### Compiling and Building
Easiest way is to run the primary build script: Easiest way is to run the primary build script:
```shell ```shell
......
...@@ -114,7 +114,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -114,7 +114,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// Contract creations are signalled by sending a transaction to the zero address. // Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.to == address(0)) { if (decodedTx.to == address(0)) {
(address created, bytes memory revertData) = Lib_SafeExecutionManagerWrapper.safeCREATE( (address created, bytes memory revertData) = Lib_SafeExecutionManagerWrapper.safeCREATE(
decodedTx.gasLimit, gasleft(),
decodedTx.data decodedTx.data
); );
...@@ -131,7 +131,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -131,7 +131,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
Lib_SafeExecutionManagerWrapper.safeINCREMENTNONCE(); Lib_SafeExecutionManagerWrapper.safeINCREMENTNONCE();
return Lib_SafeExecutionManagerWrapper.safeCALL( return Lib_SafeExecutionManagerWrapper.safeCALL(
decodedTx.gasLimit, gasleft(),
decodedTx.to, decodedTx.to,
decodedTx.data decodedTx.data
); );
......
...@@ -19,9 +19,9 @@ import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol ...@@ -19,9 +19,9 @@ import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol
/** /**
* @title OVM_L1CrossDomainMessenger * @title OVM_L1CrossDomainMessenger
* @dev The L1 Cross Domain Messenger contract sends messages from L1 to L2, and relays messages from L2 onto L1. * @dev The L1 Cross Domain Messenger contract sends messages from L1 to L2, and relays messages from L2 onto L1.
* In the event that a message sent from L1 to L2 is rejected for exceeding the L2 epoch gas limit, it can be resubmitted * In the event that a message sent from L1 to L2 is rejected for exceeding the L2 epoch gas limit, it can be resubmitted
* via this contract's replay function. * via this contract's replay function.
* *
* Compiler used: solc * Compiler used: solc
* Runtime target: EVM * Runtime target: EVM
......
...@@ -18,7 +18,7 @@ import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol ...@@ -18,7 +18,7 @@ import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol
* @title OVM_L2CrossDomainMessenger * @title OVM_L2CrossDomainMessenger
* @dev The L2 Cross Domain Messenger contract sends messages from L2 to L1, and is the entry point * @dev The L2 Cross Domain Messenger contract sends messages from L2 to L1, and is the entry point
* for L2 messages sent via the L1 Cross Domain Messenger. * for L2 messages sent via the L1 Cross Domain Messenger.
* *
* Compiler used: optimistic-solc * Compiler used: optimistic-solc
* Runtime target: OVM * Runtime target: OVM
*/ */
...@@ -75,6 +75,15 @@ contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, Abs_BaseCros ...@@ -75,6 +75,15 @@ contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, Abs_BaseCros
"Provided message has already been received." "Provided message has already been received."
); );
// Prevent calls to OVM_L2ToL1MessagePasser, which would enable
// an attacker to maliciously craft the _message to spoof
// a call from any L2 account.
if(_target == resolve("OVM_L2ToL1MessagePasser")){
// Write to the successfulMessages mapping and return immediately.
successfulMessages[xDomainCalldataHash] = true;
return;
}
xDomainMsgSender = _sender; xDomainMsgSender = _sender;
(bool success, ) = _target.call(_message); (bool success, ) = _target.call(_message);
xDomainMsgSender = DEFAULT_XDOMAIN_SENDER; xDomainMsgSender = DEFAULT_XDOMAIN_SENDER;
......
...@@ -10,6 +10,7 @@ import { iOVM_L2DepositedToken } from "../../../iOVM/bridge/tokens/iOVM_L2Deposi ...@@ -10,6 +10,7 @@ import { iOVM_L2DepositedToken } from "../../../iOVM/bridge/tokens/iOVM_L2Deposi
/* Library Imports */ /* Library Imports */
import { OVM_CrossDomainEnabled } from "../../../libraries/bridge/OVM_CrossDomainEnabled.sol"; import { OVM_CrossDomainEnabled } from "../../../libraries/bridge/OVM_CrossDomainEnabled.sol";
import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_AddressManager } from "../../../libraries/resolver/Lib_AddressManager.sol";
/** /**
* @title OVM_L1ETHGateway * @title OVM_L1ETHGateway
...@@ -36,19 +37,31 @@ contract OVM_L1ETHGateway is iOVM_L1ETHGateway, OVM_CrossDomainEnabled, Lib_Addr ...@@ -36,19 +37,31 @@ contract OVM_L1ETHGateway is iOVM_L1ETHGateway, OVM_CrossDomainEnabled, Lib_Addr
* Constructor * * Constructor *
***************/ ***************/
// This contract lives behind a proxy, so the constructor parameters will go unused.
constructor()
OVM_CrossDomainEnabled(address(0))
Lib_AddressResolver(address(0))
public
{}
/******************
* Initialization *
******************/
/** /**
* @param _libAddressManager Address manager for this OE deployment * @param _libAddressManager Address manager for this OE deployment
* @param _ovmEth L2 OVM_ETH implementation of iOVM_DepositedToken * @param _ovmEth L2 OVM_ETH implementation of iOVM_DepositedToken
*/ */
constructor( function initialize(
address _libAddressManager, address _libAddressManager,
address _ovmEth address _ovmEth
) )
OVM_CrossDomainEnabled(address(0)) // overridden in constructor code public
Lib_AddressResolver(_libAddressManager)
{ {
require(libAddressManager == Lib_AddressManager(0), "Contract has already been initialized.");
libAddressManager = Lib_AddressManager(_libAddressManager);
ovmEth = _ovmEth; ovmEth = _ovmEth;
messenger = resolve("Proxy__OVM_L1CrossDomainMessenger"); // overrides OVM_CrossDomainEnabled constructor setting because resolve() is not yet accessible messenger = resolve("Proxy__OVM_L1CrossDomainMessenger");
} }
/************** /**************
......
...@@ -480,6 +480,10 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad ...@@ -480,6 +480,10 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
assembly { assembly {
txDataLength := shr(232, calldataload(nextTransactionPtr)) txDataLength := shr(232, calldataload(nextTransactionPtr))
} }
require(
txDataLength <= MAX_ROLLUP_TX_SIZE,
"Transaction data size exceeds maximum for rollup transaction."
);
leaves[leafIndex] = _getSequencerLeafHash( leaves[leafIndex] = _getSequencerLeafHash(
curContext, curContext,
...@@ -937,7 +941,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad ...@@ -937,7 +941,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
internal internal
view view
{ {
// If there are existing elements, this batch must have the same context // If there are existing elements, this batch must have the same context
// or a later timestamp and block number. // or a later timestamp and block number.
if (getTotalElements() > 0) { if (getTotalElements() > 0) {
(,, uint40 lastTimestamp, uint40 lastBlockNumber) = _getBatchExtraData(); (,, uint40 lastTimestamp, uint40 lastBlockNumber) = _getBatchExtraData();
......
...@@ -159,7 +159,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -159,7 +159,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
override override
public public
{ {
require(transactionContext.ovmNUMBER == 0, "Only be callable at the start of a transaction"); require(transactionContext.ovmNUMBER == 0, "Only callable at the start of a transaction");
// Store our OVM_StateManager instance (significantly easier than attempting to pass the // Store our OVM_StateManager instance (significantly easier than attempting to pass the
// address around in calldata). // address around in calldata).
ovmStateManager = iOVM_StateManager(_ovmStateManager); ovmStateManager = iOVM_StateManager(_ovmStateManager);
...@@ -206,9 +206,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -206,9 +206,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// Wipe the execution context. // Wipe the execution context.
_resetContext(); _resetContext();
// Reset the ovmStateManager.
ovmStateManager = iOVM_StateManager(address(0));
} }
...@@ -640,7 +637,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -640,7 +637,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
{ {
// DELEGATECALL does not change anything about the message context. // DELEGATECALL does not change anything about the message context.
MessageContext memory nextMessageContext = messageContext; MessageContext memory nextMessageContext = messageContext;
return _callContract( return _callContract(
nextMessageContext, nextMessageContext,
_gasLimit, _gasLimit,
...@@ -915,7 +912,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -915,7 +912,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
/** /**
* Handles all interactions which involve the execution manager calling out to untrusted code (both calls and creates). * Handles all interactions which involve the execution manager calling out to untrusted code (both calls and creates).
* Ensures that OVM-related measures are enforced, including L2 gas refunds, nuisance gas, and flagged reversions. * Ensures that OVM-related measures are enforced, including L2 gas refunds, nuisance gas, and flagged reversions.
* *
* @param _nextMessageContext Message context to be used for the external message. * @param _nextMessageContext Message context to be used for the external message.
* @param _gasLimit Amount of gas to be passed into this message. * @param _gasLimit Amount of gas to be passed into this message.
* @param _contract OVM address being called or deployed to * @param _contract OVM address being called or deployed to
...@@ -1028,7 +1025,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -1028,7 +1025,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
* Handles the creation-specific safety measures required for OVM contract deployment. * Handles the creation-specific safety measures required for OVM contract deployment.
* This function sanitizes the return types for creation messages to match calls (bool, bytes). * This function sanitizes the return types for creation messages to match calls (bool, bytes).
* This allows for consistent handling of both types of messages in _handleExternalMessage(). * This allows for consistent handling of both types of messages in _handleExternalMessage().
* *
* @param _gasLimit Amount of gas to be passed into this creation. * @param _gasLimit Amount of gas to be passed into this creation.
* @param _creationCode Code to pass into CREATE for deployment. * @param _creationCode Code to pass into CREATE for deployment.
* @param _address OVM address being deployed to. * @param _address OVM address being deployed to.
...@@ -1075,7 +1072,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -1075,7 +1072,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// Actually execute the EVM create message, // Actually execute the EVM create message,
address ethAddress = Lib_EthUtils.createContract(_creationCode); address ethAddress = Lib_EthUtils.createContract(_creationCode);
if (ethAddress == address(0)) { if (ethAddress == address(0)) {
// If the creation fails, the EVM lets us grab its revert data. This may contain a revert flag // If the creation fails, the EVM lets us grab its revert data. This may contain a revert flag
// to be used above in _handleExternalMessage. // to be used above in _handleExternalMessage.
...@@ -1813,6 +1810,9 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -1813,6 +1810,9 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
messageContext.isStatic = false; messageContext.isStatic = false;
messageRecord.nuisanceGasLeft = 0; messageRecord.nuisanceGasLeft = 0;
// Reset the ovmStateManager.
ovmStateManager = iOVM_StateManager(address(0));
} }
/***************************** /*****************************
...@@ -1851,7 +1851,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -1851,7 +1851,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
if (created == address(0)) { if (created == address(0)) {
return (false, revertData); return (false, revertData);
} else { } else {
// The eth_call RPC endpoint for to = undefined will return the deployed bytecode // The eth_call RPC endpoint for to = undefined will return the deployed bytecode
// in the success case, differing from standard create messages. // in the success case, differing from standard create messages.
return (true, Lib_EthUtils.getCode(created)); return (true, Lib_EthUtils.getCode(created));
} }
......
...@@ -33,7 +33,7 @@ import { Abs_FraudContributor } from "./Abs_FraudContributor.sol"; ...@@ -33,7 +33,7 @@ import { Abs_FraudContributor } from "./Abs_FraudContributor.sol";
* This contract controls the State Manager and Execution Manager, and uses them to calculate the * This contract controls the State Manager and Execution Manager, and uses them to calculate the
* post-state root by applying the transaction. The Fraud Verifier can then check for fraud by comparing * post-state root by applying the transaction. The Fraud Verifier can then check for fraud by comparing
* the calculated post-state root with the proposed post-state root. * the calculated post-state root with the proposed post-state root.
* *
* Compiler used: solc * Compiler used: solc
* Runtime target: EVM * Runtime target: EVM
*/ */
...@@ -170,7 +170,7 @@ contract OVM_StateTransitioner is Lib_AddressResolver, Abs_FraudContributor, iOV ...@@ -170,7 +170,7 @@ contract OVM_StateTransitioner is Lib_AddressResolver, Abs_FraudContributor, iOV
{ {
return phase == TransitionPhase.COMPLETE; return phase == TransitionPhase.COMPLETE;
} }
/*********************************** /***********************************
* Public Functions: Pre-Execution * * Public Functions: Pre-Execution *
...@@ -335,7 +335,7 @@ contract OVM_StateTransitioner is Lib_AddressResolver, Abs_FraudContributor, iOV ...@@ -335,7 +335,7 @@ contract OVM_StateTransitioner is Lib_AddressResolver, Abs_FraudContributor, iOV
// We require gas to complete the logic here in run() before/after execution, // We require gas to complete the logic here in run() before/after execution,
// But must ensure the full _tx.gasLimit can be given to the ovmCALL (determinism) // But must ensure the full _tx.gasLimit can be given to the ovmCALL (determinism)
// This includes 1/64 of the gas getting lost because of EIP-150 (lost twice--first // This includes 1/64 of the gas getting lost because of EIP-150 (lost twice--first
// going into EM, then going into the code contract). // going into EM, then going into the code contract).
require( require(
gasleft() >= 100000 + _transaction.gasLimit * 1032 / 1000, // 1032/1000 = 1.032 = (64/63)^2 rounded up gasleft() >= 100000 + _transaction.gasLimit * 1032 / 1000, // 1032/1000 = 1.032 = (64/63)^2 rounded up
...@@ -354,6 +354,8 @@ contract OVM_StateTransitioner is Lib_AddressResolver, Abs_FraudContributor, iOV ...@@ -354,6 +354,8 @@ contract OVM_StateTransitioner is Lib_AddressResolver, Abs_FraudContributor, iOV
// if that's the case. // if that's the case.
ovmExecutionManager.run(_transaction, address(ovmStateManager)); ovmExecutionManager.run(_transaction, address(ovmStateManager));
// Prevent the Execution Manager from calling this SM again.
ovmStateManager.setExecutionManager(address(0));
phase = TransitionPhase.POST_EXECUTION; phase = TransitionPhase.POST_EXECUTION;
} }
......
...@@ -50,6 +50,7 @@ contract Lib_ResolvedDelegateProxy { ...@@ -50,6 +50,7 @@ contract Lib_ResolvedDelegateProxy {
fallback() fallback()
external external
payable
{ {
address target = addressManager[address(this)].getAddress((implementationName[address(this)])); address target = addressManager[address(this)].getAddress((implementationName[address(this)]));
require( require(
......
...@@ -34,7 +34,7 @@ library Lib_MerkleTree { ...@@ -34,7 +34,7 @@ library Lib_MerkleTree {
"Lib_MerkleTree: Must provide at least one leaf hash." "Lib_MerkleTree: Must provide at least one leaf hash."
); );
if (_elements.length == 0) { if (_elements.length == 1) {
return _elements[0]; return _elements[0];
} }
...@@ -203,7 +203,7 @@ library Lib_MerkleTree { ...@@ -203,7 +203,7 @@ library Lib_MerkleTree {
// Borrowed with <3 from https://github.com/ethereum/solidity-examples // Borrowed with <3 from https://github.com/ethereum/solidity-examples
uint256 val = _in; uint256 val = _in;
uint256 highest = 0; uint256 highest = 0;
for (uint8 i = 128; i >= 1; i >>= 1) { for (uint256 i = 128; i >= 1; i >>= 1) {
if (val & (uint(1) << i) - 1 << i != 0) { if (val & (uint(1) << i) - 1 << i != 0) {
highest += i; highest += i;
val >>= i; val >>= i;
......
import { HardhatUserConfig } from 'hardhat/types' import { HardhatUserConfig } from 'hardhat/types'
import 'solidity-coverage'
import { import {
DEFAULT_ACCOUNTS_HARDHAT, DEFAULT_ACCOUNTS_HARDHAT,
...@@ -17,6 +18,11 @@ const config: HardhatUserConfig = { ...@@ -17,6 +18,11 @@ const config: HardhatUserConfig = {
accounts: DEFAULT_ACCOUNTS_HARDHAT, accounts: DEFAULT_ACCOUNTS_HARDHAT,
blockGasLimit: RUN_OVM_TEST_GAS * 2, blockGasLimit: RUN_OVM_TEST_GAS * 2,
}, },
// Add this network to your config!
optimism: {
url: 'http://127.0.0.1:8545',
ovm: true,
},
}, },
mocha: { mocha: {
timeout: 50000, timeout: 50000,
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
"build:mainnet": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy && CHAIN_ID=10 yarn run build:dump && yarn run build:typechain", "build:mainnet": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy && CHAIN_ID=10 yarn run build:dump && yarn run build:typechain",
"build:typescript": "tsc -p ./tsconfig.build.json", "build:typescript": "tsc -p ./tsconfig.build.json",
"build:contracts": "hardhat compile --show-stack-traces", "build:contracts": "hardhat compile --show-stack-traces",
"build:contracts:ovm": "TARGET=ovm hardhat compile --show-stack-traces", "build:contracts:ovm": "hardhat compile --network optimism",
"build:dump": "ts-node \"bin/take-dump.ts\"", "build:dump": "ts-node \"bin/take-dump.ts\"",
"build:copy": "yarn run build:copy:artifacts && yarn build:copy:artifacts:ovm && yarn run build:copy:contracts", "build:copy": "yarn run build:copy:artifacts && yarn build:copy:artifacts:ovm && yarn run build:copy:contracts",
"build:copy:artifacts": "copyfiles -u 1 \"artifacts/**/*.json\" \"dist/artifacts\"", "build:copy:artifacts": "copyfiles -u 1 \"artifacts/**/*.json\" \"dist/artifacts\"",
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
"mocha": "^8.3.0", "mocha": "^8.3.0",
"random-bytes-seed": "^1.0.3", "random-bytes-seed": "^1.0.3",
"rlp": "^2.2.6", "rlp": "^2.2.6",
"solidity-coverage": "^0.7.16",
"ts-generator": "0.0.8", "ts-generator": "0.0.8",
"typechain": "2.0.0" "typechain": "2.0.0"
}, },
......
yarn run build:typescript & yarn run build:typescript &
yarn run build:contracts:ovm &
yarn run build:contracts & yarn run build:contracts &
# avoid race condition between the 2 concurrent hardhat instances
sleep 2
yarn run build:contracts:ovm &
wait wait
......
...@@ -109,10 +109,23 @@ export const makeContractDeployConfig = async ( ...@@ -109,10 +109,23 @@ export const makeContractDeployConfig = async (
}, },
OVM_L1ETHGateway: { OVM_L1ETHGateway: {
factory: getContractFactory('OVM_L1ETHGateway'), factory: getContractFactory('OVM_L1ETHGateway'),
params: [ params: [],
AddressManager.address, },
'0x4200000000000000000000000000000000000006', Proxy__OVM_L1ETHGateway: {
], factory: getContractFactory('Lib_ResolvedDelegateProxy'),
params: [AddressManager.address, 'OVM_L1ETHGateway'],
afterDeploy: async (contracts): Promise<void> => {
const l1EthGateway = getContractFactory('OVM_L1ETHGateway')
.connect(config.deploymentSigner)
.attach(contracts.Proxy__OVM_L1ETHGateway.address)
await _sendTx(
l1EthGateway.initialize(
AddressManager.address,
'0x4200000000000000000000000000000000000006',
config.deployOverrides
)
)
},
}, },
OVM_L1MultiMessageRelayer: { OVM_L1MultiMessageRelayer: {
factory: getContractFactory('OVM_L1MultiMessageRelayer'), factory: getContractFactory('OVM_L1MultiMessageRelayer'),
......
...@@ -106,7 +106,6 @@ describe('OVM_ECDSAContractAccount', () => { ...@@ -106,7 +106,6 @@ describe('OVM_ECDSAContractAccount', () => {
// The ovmCALL is the 2nd call because the first call transfers the fee. // The ovmCALL is the 2nd call because the first call transfers the fee.
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1] const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1]
expect(ovmCALL._gasLimit).to.equal(DEFAULT_EIP155_TX.gasLimit)
expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to) expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to)
expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data) expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data)
}) })
...@@ -130,7 +129,6 @@ describe('OVM_ECDSAContractAccount', () => { ...@@ -130,7 +129,6 @@ describe('OVM_ECDSAContractAccount', () => {
// The ovmCALL is the 2nd call because the first call transfers the fee. // The ovmCALL is the 2nd call because the first call transfers the fee.
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1] const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1]
expect(ovmCALL._gasLimit).to.equal(DEFAULT_EIP155_TX.gasLimit)
expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to) expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to)
expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data) expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data)
}) })
......
...@@ -13,6 +13,7 @@ const L1_ETH_GATEWAY_NAME = 'Proxy__OVM_L1CrossDomainMessenger' ...@@ -13,6 +13,7 @@ const L1_ETH_GATEWAY_NAME = 'Proxy__OVM_L1CrossDomainMessenger'
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated' const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
const ERR_INVALID_X_DOMAIN_MSG_SENDER = const ERR_INVALID_X_DOMAIN_MSG_SENDER =
'OVM_XCHAIN: wrong sender of cross-domain message' 'OVM_XCHAIN: wrong sender of cross-domain message'
const ERR_ALREADY_INITIALIZED = 'Contract has already been initialized.'
describe('OVM_L1ETHGateway', () => { describe('OVM_L1ETHGateway', () => {
// init signers // init signers
...@@ -45,14 +46,29 @@ describe('OVM_L1ETHGateway', () => { ...@@ -45,14 +46,29 @@ describe('OVM_L1ETHGateway', () => {
{ address: await l1MessengerImpersonator.getAddress() } // This allows us to use an ethers override {from: Mock__OVM_L2CrossDomainMessenger.address} to mock calls { address: await l1MessengerImpersonator.getAddress() } // This allows us to use an ethers override {from: Mock__OVM_L2CrossDomainMessenger.address} to mock calls
) )
// Deploy the contract under test // Deploy the contract under test and initialize
OVM_L1ETHGateway = await ( OVM_L1ETHGateway = await (
await ethers.getContractFactory('OVM_L1ETHGateway') await ethers.getContractFactory('OVM_L1ETHGateway')
).deploy(AddressManager.address, Mock__OVM_L2DepositedERC20.address) ).deploy()
await OVM_L1ETHGateway.initialize(
AddressManager.address,
Mock__OVM_L2DepositedERC20.address
)
finalizeDepositGasLimit = await OVM_L1ETHGateway.getFinalizeDepositL2Gas() finalizeDepositGasLimit = await OVM_L1ETHGateway.getFinalizeDepositL2Gas()
}) })
describe('initialize', () => {
it('Should only be callable once', async () => {
await expect(
OVM_L1ETHGateway.initialize(
ethers.constants.AddressZero,
ethers.constants.AddressZero
)
).to.be.revertedWith(ERR_ALREADY_INITIALIZED)
})
})
describe('finalizeWithdrawal', () => { describe('finalizeWithdrawal', () => {
it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L1 account', async () => { it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L1 account', async () => {
// Deploy new gateway, initialize with random messenger // Deploy new gateway, initialize with random messenger
...@@ -72,7 +88,11 @@ describe('OVM_L1ETHGateway', () => { ...@@ -72,7 +88,11 @@ describe('OVM_L1ETHGateway', () => {
OVM_L1ETHGateway = await ( OVM_L1ETHGateway = await (
await ethers.getContractFactory('OVM_L1ETHGateway') await ethers.getContractFactory('OVM_L1ETHGateway')
).deploy(AddressManager.address, Mock__OVM_L2DepositedERC20.address) ).deploy()
await OVM_L1ETHGateway.initialize(
AddressManager.address,
Mock__OVM_L2DepositedERC20.address
)
Mock__OVM_L1CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with( Mock__OVM_L1CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
NON_ZERO_ADDRESS NON_ZERO_ADDRESS
...@@ -147,10 +167,14 @@ describe('OVM_L1ETHGateway', () => { ...@@ -147,10 +167,14 @@ describe('OVM_L1ETHGateway', () => {
Mock__OVM_L1CrossDomainMessenger.address Mock__OVM_L1CrossDomainMessenger.address
) )
// Deploy the contract under test: // Deploy the contract under test and initialize
OVM_L1ETHGateway = await ( OVM_L1ETHGateway = await (
await ethers.getContractFactory('OVM_L1ETHGateway') await ethers.getContractFactory('OVM_L1ETHGateway')
).deploy(AddressManager.address, Mock__OVM_L2DepositedERC20.address) ).deploy()
await OVM_L1ETHGateway.initialize(
AddressManager.address,
Mock__OVM_L2DepositedERC20.address
)
}) })
it('deposit() escrows the deposit amount and sends the correct deposit message', async () => { it('deposit() escrows the deposit amount and sends the correct deposit message', async () => {
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
getXDomainCalldata, getXDomainCalldata,
} from '../../../../helpers' } from '../../../../helpers'
import { solidityKeccak256 } from 'ethers/lib/utils'
describe('OVM_L2CrossDomainMessenger', () => { describe('OVM_L2CrossDomainMessenger', () => {
let signer: Signer let signer: Signer
...@@ -157,5 +158,44 @@ describe('OVM_L2CrossDomainMessenger', () => { ...@@ -157,5 +158,44 @@ describe('OVM_L2CrossDomainMessenger', () => {
OVM_L2CrossDomainMessenger.relayMessage(target, sender, message, 0) OVM_L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
).to.be.revertedWith('Provided message has already been received.') ).to.be.revertedWith('Provided message has already been received.')
}) })
it('should not make a call if the target is the L2 MessagePasser', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
Mock__OVM_L1CrossDomainMessenger.address
)
target = await AddressManager.getAddress('OVM_L2ToL1MessagePasser')
message = Mock__OVM_L2ToL1MessagePasser.interface.encodeFunctionData(
'passMessageToL1(bytes)',
[NON_NULL_BYTES32]
)
const resProm = OVM_L2CrossDomainMessenger.relayMessage(
target,
sender,
message,
0
)
// The call to relayMessage() should succeed.
await expect(resProm).to.not.be.reverted
// There should be no 'relayedMessage' event logged in the receipt.
const logs = (
await Mock__OVM_L2ToL1MessagePasser.provider.getTransactionReceipt(
(await resProm).hash
)
).logs
expect(logs).to.deep.equal([])
// The message should be registered as successful.
expect(
await OVM_L2CrossDomainMessenger.successfulMessages(
solidityKeccak256(
['bytes'],
[getXDomainCalldata(await signer.getAddress(), target, message, 0)]
)
)
).to.be.true
})
}) })
}) })
...@@ -129,7 +129,7 @@ describe('[GAS BENCHMARK] OVM_CanonicalTransactionChain', () => { ...@@ -129,7 +129,7 @@ describe('[GAS BENCHMARK] OVM_CanonicalTransactionChain', () => {
) )
}) })
describe('appendSequencerBatch', () => { describe('appendSequencerBatch [ @skip-on-coverage ]', () => {
beforeEach(() => { beforeEach(() => {
OVM_CanonicalTransactionChain = OVM_CanonicalTransactionChain.connect( OVM_CanonicalTransactionChain = OVM_CanonicalTransactionChain.connect(
sequencer sequencer
......
...@@ -754,6 +754,32 @@ describe('OVM_CanonicalTransactionChain', () => { ...@@ -754,6 +754,32 @@ describe('OVM_CanonicalTransactionChain', () => {
).to.be.revertedWith('Must append at least one element.') ).to.be.revertedWith('Must append at least one element.')
}) })
it('should revert when trying to input more data than the max data size', async () => {
const MAX_ROLLUP_TX_SIZE = await OVM_CanonicalTransactionChain.MAX_ROLLUP_TX_SIZE()
const data = '0x' + '12'.repeat(MAX_ROLLUP_TX_SIZE + 1)
const timestamp = await getEthTime(ethers.provider)
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
await expect(
appendSequencerBatch(OVM_CanonicalTransactionChain, {
transactions: [data],
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp,
blockNumber,
},
],
shouldStartAtElement: 0,
totalElementsToAppend: 1,
})
).to.be.revertedWith(
'Transaction data size exceeds maximum for rollup transaction.'
)
})
describe('Sad path cases', () => { describe('Sad path cases', () => {
const target = NON_ZERO_ADDRESS const target = NON_ZERO_ADDRESS
const gasLimit = 500_000 const gasLimit = 500_000
......
...@@ -101,7 +101,7 @@ describe('OVM_ExecutionManager gas consumption', () => { ...@@ -101,7 +101,7 @@ describe('OVM_ExecutionManager gas consumption', () => {
).connect(wallet) ).connect(wallet)
}) })
describe('Measure cost of a very simple contract', async () => { describe('Measure cost of a very simple contract [ @skip-on-coverage ]', async () => {
it('Gas cost of run', async () => { it('Gas cost of run', async () => {
const gasCost = await gasMeasurement.getGasCost( const gasCost = await gasMeasurement.getGasCost(
OVM_ExecutionManager, OVM_ExecutionManager,
......
...@@ -21,7 +21,7 @@ const DUMMY_KEY = DUMMY_BYTES32[0] ...@@ -21,7 +21,7 @@ const DUMMY_KEY = DUMMY_BYTES32[0]
const DUMMY_VALUE_1 = DUMMY_BYTES32[1] const DUMMY_VALUE_1 = DUMMY_BYTES32[1]
const DUMMY_VALUE_2 = DUMMY_BYTES32[2] const DUMMY_VALUE_2 = DUMMY_BYTES32[2]
describe('OVM_StateManager gas consumption', () => { describe('OVM_StateManager gas consumption [ @skip-on-coverage ]', () => {
let owner: Signer let owner: Signer
before(async () => { before(async () => {
;[owner] = await ethers.getSigners() ;[owner] = await ethers.getSigners()
......
This diff is collapsed.
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
}, },
"devDependencies": { "devDependencies": {
"@ethersproject/abstract-provider": "^5.1.0",
"@nomiclabs/hardhat-ethers": "^2.0.1", "@nomiclabs/hardhat-ethers": "^2.0.1",
"@types/browser-or-node": "^1.3.0", "@types/browser-or-node": "^1.3.0",
"@types/cors": "^2.8.9", "@types/cors": "^2.8.9",
......
...@@ -12,6 +12,8 @@ import { ...@@ -12,6 +12,8 @@ import {
import { import {
DecodedSequencerBatchTransaction, DecodedSequencerBatchTransaction,
EventArgsSequencerBatchAppended, EventArgsSequencerBatchAppended,
SequencerBatchAppendedExtraData,
SequencerBatchAppendedParsedEvent,
TransactionBatchEntry, TransactionBatchEntry,
TransactionEntry, TransactionEntry,
EventHandlerSet, EventHandlerSet,
...@@ -21,27 +23,6 @@ import { ...@@ -21,27 +23,6 @@ import {
SEQUENCER_GAS_LIMIT, SEQUENCER_GAS_LIMIT,
} from '../../../utils' } from '../../../utils'
export interface SequencerBatchAppendedExtraData {
timestamp: number
blockNumber: number
submitter: string
l1TransactionData: string
l1TransactionHash: string
gasLimit: number
// Stuff from TransactionBatchAppended.
prevTotalElements: BigNumber
batchIndex: BigNumber
batchSize: BigNumber
batchRoot: string
batchExtraData: string
}
export interface SequencerBatchAppendedParsedEvent {
transactionBatchEntry: TransactionBatchEntry
transactionEntries: TransactionEntry[]
}
export const handleEventsSequencerBatchAppended: EventHandlerSet< export const handleEventsSequencerBatchAppended: EventHandlerSet<
EventArgsSequencerBatchAppended, EventArgsSequencerBatchAppended,
SequencerBatchAppendedExtraData, SequencerBatchAppendedExtraData,
......
...@@ -6,23 +6,16 @@ import { BigNumber } from 'ethers' ...@@ -6,23 +6,16 @@ import { BigNumber } from 'ethers'
import { import {
EventArgsStateBatchAppended, EventArgsStateBatchAppended,
StateRootBatchEntry, StateRootBatchEntry,
StateBatchAppendedExtraData,
StateBatchAppendedParsedEvent,
StateRootEntry, StateRootEntry,
EventHandlerSet, EventHandlerSet,
} from '../../../types' } from '../../../types'
export const handleEventsStateBatchAppended: EventHandlerSet< export const handleEventsStateBatchAppended: EventHandlerSet<
EventArgsStateBatchAppended, EventArgsStateBatchAppended,
{ StateBatchAppendedExtraData,
timestamp: number StateBatchAppendedParsedEvent
blockNumber: number
submitter: string
l1TransactionHash: string
l1TransactionData: string
},
{
stateRootBatchEntry: StateRootBatchEntry
stateRootEntries: StateRootEntry[]
}
> = { > = {
getExtraData: async (event) => { getExtraData: async (event) => {
const eventBlock = await event.getBlock() const eventBlock = await event.getBlock()
......
import { JsonRpcProvider } from '@ethersproject/providers' import { JsonRpcProvider } from '@ethersproject/providers'
import { BigNumber } from 'ethers'
import { TransportDB } from '../db/transport-db' import { TransportDB } from '../db/transport-db'
import { TypedEthersEvent } from './event-types' import { TypedEthersEvent } from './event-types'
import {
TransactionBatchEntry,
TransactionEntry,
StateRootBatchEntry,
StateRootEntry,
} from './database-types'
export type GetExtraDataHandler<TEventArgs, TExtraData> = ( export type GetExtraDataHandler<TEventArgs, TExtraData> = (
event?: TypedEthersEvent<TEventArgs>, event?: TypedEthersEvent<TEventArgs>,
...@@ -22,3 +30,37 @@ export interface EventHandlerSet<TEventArgs, TExtraData, TParsedEvent> { ...@@ -22,3 +30,37 @@ export interface EventHandlerSet<TEventArgs, TExtraData, TParsedEvent> {
parseEvent: ParseEventHandler<TEventArgs, TExtraData, TParsedEvent> parseEvent: ParseEventHandler<TEventArgs, TExtraData, TParsedEvent>
storeEvent: StoreEventHandler<TParsedEvent> storeEvent: StoreEventHandler<TParsedEvent>
} }
export interface SequencerBatchAppendedExtraData {
timestamp: number
blockNumber: number
submitter: string
l1TransactionData: string
l1TransactionHash: string
gasLimit: number
// Stuff from TransactionBatchAppended.
prevTotalElements: BigNumber
batchIndex: BigNumber
batchSize: BigNumber
batchRoot: string
batchExtraData: string
}
export interface SequencerBatchAppendedParsedEvent {
transactionBatchEntry: TransactionBatchEntry
transactionEntries: TransactionEntry[]
}
export interface StateBatchAppendedExtraData {
timestamp: number
blockNumber: number
submitter: string
l1TransactionHash: string
l1TransactionData: string
}
export interface StateBatchAppendedParsedEvent {
stateRootBatchEntry: StateRootBatchEntry
stateRootEntries: StateRootEntry[]
}
import { BigNumber, ethers } from 'ethers' import { BigNumber, ethers } from 'ethers'
import { expect } from '../../../../setup' import { expect } from '../../../../setup'
import { import {
SequencerBatchAppendedExtraData,
validateBatchTransaction, validateBatchTransaction,
handleEventsSequencerBatchAppended, handleEventsSequencerBatchAppended,
} from '../../../../../src/services/l1-ingestion/handlers/sequencer-batch-appended' } from '../../../../../src/services/l1-ingestion/handlers/sequencer-batch-appended'
import { SequencerBatchAppendedExtraData } from '../../../../../src/types'
import { l1TransactionData } from '../../../examples/l1-data' import { l1TransactionData } from '../../../examples/l1-data'
import { blocksOnL2 } from '../../../examples/l2-data' import { blocksOnL2 } from '../../../examples/l2-data'
......
import { BigNumber } from 'ethers'
import { Block } from '@ethersproject/abstract-provider'
import { expect } from '../../../../setup'
import { handleEventsStateBatchAppended } from '../../../../../src/services/l1-ingestion/handlers/state-batch-appended'
import { StateBatchAppendedExtraData } from '../../../../../src/types'
import { l1StateBatchData } from '../../../examples/l1-data'
describe('Event Handlers: OVM_CanonicalTransactionChain.StateBatchAppended', () => {
describe('getExtraData', () => {
it('should return event block and transaction', async () => {
// Source: https://etherscan.io/tx/0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49
const l1Transaction = {
hash:
'0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49',
from: '0xfd7d4de366850c08ee2cba32d851385a3071ec8d',
data: l1StateBatchData,
}
// Source: https://etherscan.io/block/12106615
const eventBlock: Block = {
timestamp: 1616680530,
number: 12106615,
hash:
'0x9c40310e19e943ad38e170329465c4489f6aba5895e9cacdac236be181aea31f',
parentHash:
'0xc7707a04c287a22ff4e43e5d9316e45ab342dcd405e7e0284eb51ce71a3a29ac',
miner: '0xea674fdde714fd979de3edf0f56aa9716b898ec8',
nonce: '0x40e6174f521a7cd8',
difficulty: 5990647962682594,
gasLimit: BigNumber.from(548976),
gasUsed: BigNumber.from(12495850),
extraData: '0x65746865726d696e652d6575726f70652d7765737433',
transactions: [l1Transaction.hash],
}
const input1: [any] = [
{
getBlock: () => eventBlock,
getTransaction: () => l1Transaction,
},
]
const output1 = await handleEventsStateBatchAppended.getExtraData(
...input1
)
expect(output1.timestamp).to.equal(eventBlock.timestamp)
expect(output1.blockNumber).to.equal(eventBlock.number)
expect(output1.submitter).to.equal(l1Transaction.from)
expect(output1.l1TransactionHash).to.equal(l1Transaction.hash)
expect(output1.l1TransactionData).to.equal(l1Transaction.data)
})
})
describe('parseEvent', () => {
it('should have a ctcIndex equal to null', () => {
// Source: https://etherscan.io/tx/0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49#eventlog
const event = {
args: {
_batchIndex: BigNumber.from(144),
_batchRoot:
'AD2039C6E9A8EE58817252CF16AB720BF3ED20CC4B53184F5B11DE09639AA123',
_batchSize: BigNumber.from(522),
_prevTotalElements: BigNumber.from(96000),
_extraData:
'00000000000000000000000000000000000000000000000000000000605C33E2000000000000000000000000FD7D4DE366850C08EE2CBA32D851385A3071EC8D',
},
}
const extraData: StateBatchAppendedExtraData = {
l1TransactionData: l1StateBatchData,
timestamp: 1616680530,
blockNumber: 12106615,
submitter: '0xfd7d4de366850c08ee2cba32d851385a3071ec8d',
l1TransactionHash:
'0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49',
}
const input1: [any, StateBatchAppendedExtraData] = [event, extraData]
const output1 = handleEventsStateBatchAppended.parseEvent(...input1)
expect(output1.stateRootEntries.length).to.eq(
event.args._batchSize.toNumber()
)
output1.stateRootEntries.forEach((entry, i) => {
expect(entry.index).to.eq(
event.args._prevTotalElements.add(BigNumber.from(i)).toNumber()
)
expect(entry.batchIndex).to.eq(event.args._batchIndex.toNumber())
expect(entry.confirmed).to.be.true
})
const batchEntry = output1.stateRootBatchEntry
expect(batchEntry.index).to.eq(event.args._batchIndex.toNumber())
expect(batchEntry.blockNumber).to.eq(extraData.blockNumber)
expect(batchEntry.timestamp).to.eq(extraData.timestamp)
expect(batchEntry.submitter).to.eq(extraData.submitter)
expect(batchEntry.size).to.eq(event.args._batchSize.toNumber())
expect(batchEntry.root).to.eq(event.args._batchRoot)
expect(batchEntry.prevTotalElements).to.eq(
event.args._prevTotalElements.toNumber()
)
expect(batchEntry.extraData).to.eq(event.args._extraData)
expect(batchEntry.l1TransactionHash).to.eq(extraData.l1TransactionHash)
})
})
})
...@@ -75,7 +75,7 @@ const getOvmSolcPath = async (version: string): Promise<string> => { ...@@ -75,7 +75,7 @@ const getOvmSolcPath = async (version: string): Promise<string> => {
subtask( subtask(
TASK_COMPILE_SOLIDITY_RUN_SOLC, TASK_COMPILE_SOLIDITY_RUN_SOLC,
async (args: { input: any; solcPath: string }, hre, runSuper) => { async (args: { input: any; solcPath: string }, hre, runSuper) => {
if ((hre.network as any).ovm !== true) { if (hre.network.ovm !== true) {
return runSuper(args) return runSuper(args)
} }
...@@ -127,9 +127,9 @@ subtask( ...@@ -127,9 +127,9 @@ subtask(
) )
extendEnvironment((hre) => { extendEnvironment((hre) => {
if (process.env.TARGET === 'ovm') { if (hre.network.config.ovm) {
;(hre.network as any).ovm = true hre.network.ovm = hre.network.config.ovm
// Quick check to make sure we don't accidentally perform this transform multiple times.
let artifactsPath = hre.config.paths.artifacts let artifactsPath = hre.config.paths.artifacts
if (!artifactsPath.endsWith('-ovm')) { if (!artifactsPath.endsWith('-ovm')) {
artifactsPath = artifactsPath + '-ovm' artifactsPath = artifactsPath + '-ovm'
...@@ -144,6 +144,5 @@ extendEnvironment((hre) => { ...@@ -144,6 +144,5 @@ extendEnvironment((hre) => {
hre.config.paths.artifacts = artifactsPath hre.config.paths.artifacts = artifactsPath
hre.config.paths.cache = cachePath hre.config.paths.cache = cachePath
;(hre as any).artifacts = new Artifacts(artifactsPath) ;(hre as any).artifacts = new Artifacts(artifactsPath)
;(hre.network as any).ovm = true
} }
}) })
...@@ -12,4 +12,26 @@ declare module 'hardhat/types/config' { ...@@ -12,4 +12,26 @@ declare module 'hardhat/types/config' {
solcVersion?: string solcVersion?: string
} }
} }
interface HardhatNetworkUserConfig {
ovm?: boolean
}
interface HttpNetworkUserConfig {
ovm?: boolean
}
interface HardhatNetworkConfig {
ovm: boolean
}
interface HttpNetworkConfig {
ovm: boolean
}
}
declare module 'hardhat/types/runtime' {
interface Network {
ovm: boolean
}
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
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