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:
- name: Install integration test dependencies
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
- name: Build deps for the integration tests
......
......@@ -15,8 +15,8 @@ on:
- 'packages/**/*.ts'
jobs:
build-test-lint:
name: Run job on ${{matrix.node}}
test:
name: Run tests on ${{matrix.node}}
runs-on: ubuntu-latest
strategy:
......@@ -48,14 +48,42 @@ jobs:
- name: Install Dependencies
# 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
- name: Build
run: yarn build
- name: Lint
run: yarn lint
- 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 = {
mocha: {
timeout: 100000,
},
networks: {
optimism: {
url: 'http://localhost:8545',
ovm: true,
},
},
}
export default config
......@@ -8,7 +8,7 @@
"lint": "yarn lint:fix && yarn lint:check",
"lint:check": "tslint --format stylish --project .",
"lint:fix": "prettier --config ./prettier-config.json --write 'test/**/*.ts'",
"test:integration": "TARGET=ovm hardhat test"
"test:integration": "hardhat --network optimism test"
},
"devDependencies": {
"@eth-optimism/contracts": "^0.1.11",
......
......@@ -2,9 +2,9 @@ ADDRESS_MANAGER_ADDRESS=
DEBUG=info*,error*,warn*,debug*
MAX_TX_SIZE=90000
MIN_TX_SIZE=0
MAX_BATCH_SIZE=50
MAX_L1_TX_SIZE=90000
MIN_L1_TX_SIZE=0
MAX_TX_BATCH_COUNT=50
POLL_INTERVAL=15000
NUM_CONFIRMATIONS=0
......
......@@ -13,8 +13,10 @@
"scripts": {
"clean": "yarn lerna run clean",
"build": "yarn lerna run build",
"test": "yarn lerna run test --parallel --since origin/master",
"lint": "yarn lerna run lint --parallel --since origin/master",
"test": "yarn lerna run test --parallel",
"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"
}
}
build
dist
node_modules
.env
cache/*
# vim
*.swp
{
"name": "@eth-optimism/batch-submitter",
"version": "0.1.5",
"version": "0.1.7",
"description": "[Optimism] Batch submission for sequencer & aggregators",
"main": "dist/index",
"types": "dist/index",
......@@ -10,7 +10,7 @@
"scripts": {
"start": "node ./exec/run-batch-submitter.js",
"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:check": "tslint --format stylish --project .",
"lint:fix": "prettier --config prettier-config.json --write \"hardhat.config.ts\" \"{src,exec,test}/**/*.ts\"",
......
......@@ -131,9 +131,10 @@ export abstract class BatchSubmitter {
}
protected _shouldSubmitBatch(batchSizeInBytes: number): boolean {
const currentTimestamp = Date.now()
const isTimeoutReached =
this.lastBatchSubmissionTimestamp + this.maxBatchSubmissionTime <=
Date.now()
currentTimestamp
if (batchSizeInBytes < this.minTxSize) {
if (!isTimeoutReached) {
this.log.info(
......@@ -141,12 +142,24 @@ export abstract class BatchSubmitter {
{
batchSizeInBytes,
minTxSize: this.minTxSize,
lastBatchSubmissionTimestamp: this.lastBatchSubmissionTimestamp,
currentTimestamp,
}
)
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
}
......
/* External Imports */
import { Promise as bPromise } from 'bluebird'
import { Contract, Signer } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { getContractFactory } from '@eth-optimism/contracts'
......@@ -108,14 +109,13 @@ export class StateBatchSubmitter extends BatchSubmitter {
const startBlock: number =
(await this.chainContract.getTotalElements()).toNumber() + BLOCK_OFFSET
// 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 =
(await this.ctcContract.getTotalElements()).toNumber() + BLOCK_OFFSET
const endBlock: number = Math.min(
startBlock + this.maxBatchSize,
totalElements
)
if (startBlock >= endBlock) {
if (startBlock > endBlock) {
this.log.error(
......@@ -142,7 +142,7 @@ export class StateBatchSubmitter extends BatchSubmitter {
'appendStateBatch',
[batch, startBlock]
)
if (!this._shouldSubmitBatch(tx.length * 2)) {
if (!this._shouldSubmitBatch(tx.length / 2)) {
return
}
......@@ -172,21 +172,22 @@ export class StateBatchSubmitter extends BatchSubmitter {
startBlock: number,
endBlock: number
): Promise<Bytes32[]> {
const batch: Bytes32[] = []
for (let i = startBlock; i < endBlock; i++) {
const block = (await this.l2Provider.getBlockWithTransactions(
i
)) as L2Block
if (block.transactions[0].from === this.fraudSubmissionAddress) {
batch.push(
'0xbad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1'
)
this.fraudSubmissionAddress = 'no fraud'
} else {
batch.push(block.stateRoot)
}
}
const blockRange = endBlock - startBlock
const batch: Bytes32[] = await bPromise.map(
[...Array(blockRange).keys()],
async (i: number) => {
this.log.debug('Fetching L2BatchElement', { blockNo: startBlock + i })
const block = (await this.l2Provider.getBlockWithTransactions(
startBlock + i
)) as L2Block
if (block.transactions[0].from === this.fraudSubmissionAddress) {
this.fraudSubmissionAddress = 'no fraud'
return '0xbad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1'
}
return block.stateRoot
},
{ concurrency: 100 }
)
let tx = this.chainContract.interface.encodeFunctionData(
'appendStateBatch',
......@@ -199,6 +200,7 @@ export class StateBatchSubmitter extends BatchSubmitter {
startBlock,
])
}
return batch
}
}
......@@ -237,7 +237,12 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
batchParams,
wasBatchTruncated,
] = 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)) {
return
}
......@@ -278,7 +283,7 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
this.log.debug('Fetching L2BatchElement', { blockNo: startBlock + i })
return this._getL2BatchElement(startBlock + i)
},
{ concurrency: 50 }
{ concurrency: 100 }
)
// Fix our batches if we are configured to. TODO: Remove this.
......
......@@ -32,11 +32,13 @@ interface RequiredEnvVars {
// The layer one address manager address
ADDRESS_MANAGER_ADDRESS: 'ADDRESS_MANAGER_ADDRESS'
// 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.
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.
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.
MAX_BATCH_SUBMISSION_TIME: 'MAX_BATCH_SUBMISSION_TIME'
// The delay in milliseconds between querying L2 for more transactions / to create a new batch.
......@@ -62,9 +64,10 @@ const requiredEnvVars: RequiredEnvVars = {
L1_NODE_WEB3_URL: 'L1_NODE_WEB3_URL',
L2_NODE_WEB3_URL: 'L2_NODE_WEB3_URL',
ADDRESS_MANAGER_ADDRESS: 'ADDRESS_MANAGER_ADDRESS',
MIN_TX_SIZE: 'MIN_TX_SIZE',
MAX_TX_SIZE: 'MAX_TX_SIZE',
MAX_BATCH_SIZE: 'MAX_BATCH_SIZE',
MIN_L1_TX_SIZE: 'MIN_L1_TX_SIZE',
MAX_L1_TX_SIZE: 'MAX_L1_TX_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',
POLL_INTERVAL: 'POLL_INTERVAL',
NUM_CONFIRMATIONS: 'NUM_CONFIRMATIONS',
......@@ -144,9 +147,9 @@ export const run = async () => {
const txBatchSubmitter = new TransactionBatchSubmitter(
sequencerSigner,
l2Provider,
parseInt(requiredEnvVars.MIN_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_BATCH_SIZE, 10),
parseInt(requiredEnvVars.MIN_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_TX_BATCH_COUNT, 10),
parseInt(requiredEnvVars.MAX_BATCH_SUBMISSION_TIME, 10) * 1_000,
parseInt(requiredEnvVars.NUM_CONFIRMATIONS, 10),
parseInt(requiredEnvVars.RESUBMISSION_TIMEOUT, 10) * 1_000,
......@@ -164,9 +167,9 @@ export const run = async () => {
const stateBatchSubmitter = new StateBatchSubmitter(
sequencerSigner,
l2Provider,
parseInt(requiredEnvVars.MIN_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_BATCH_SIZE, 10),
parseInt(requiredEnvVars.MIN_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_L1_TX_SIZE, 10),
parseInt(requiredEnvVars.MAX_STATE_BATCH_COUNT, 10),
parseInt(requiredEnvVars.MAX_BATCH_SUBMISSION_TIME, 10) * 1_000,
parseInt(requiredEnvVars.NUM_CONFIRMATIONS, 10),
parseInt(requiredEnvVars.RESUBMISSION_TIMEOUT, 10) * 1_000,
......
......@@ -88,6 +88,7 @@ export class MockchainProvider extends OptimismProvider {
public setL2BlockData(
tx: L2Transaction,
timestamp?: number,
stateRoot?: string,
start: number = 1,
end: number = this.mockBlocks.length
) {
......@@ -99,6 +100,7 @@ export class MockchainProvider extends OptimismProvider {
...this.mockBlocks[i].transactions[0],
...tx,
}
this.mockBlocks[i].stateRoot = stateRoot
}
}
......
......@@ -274,6 +274,7 @@ describe('TransactionBatchSubmitter', () => {
l1TxOrigin: null,
} as any,
nextQueueElement.timestamp - 1,
'', // blank state root
3,
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:
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
Easiest way is to run the primary build script:
```shell
......
......@@ -114,7 +114,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.to == address(0)) {
(address created, bytes memory revertData) = Lib_SafeExecutionManagerWrapper.safeCREATE(
decodedTx.gasLimit,
gasleft(),
decodedTx.data
);
......@@ -131,7 +131,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
Lib_SafeExecutionManagerWrapper.safeINCREMENTNONCE();
return Lib_SafeExecutionManagerWrapper.safeCALL(
decodedTx.gasLimit,
gasleft(),
decodedTx.to,
decodedTx.data
);
......
......@@ -19,9 +19,9 @@ import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol
/**
* @title OVM_L1CrossDomainMessenger
* @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
* via this contract's replay function.
* @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
* via this contract's replay function.
*
* Compiler used: solc
* Runtime target: EVM
......
......@@ -18,7 +18,7 @@ import { Abs_BaseCrossDomainMessenger } from "./Abs_BaseCrossDomainMessenger.sol
* @title OVM_L2CrossDomainMessenger
* @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.
*
*
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
......@@ -75,6 +75,15 @@ contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, Abs_BaseCros
"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;
(bool success, ) = _target.call(_message);
xDomainMsgSender = DEFAULT_XDOMAIN_SENDER;
......
......@@ -10,6 +10,7 @@ import { iOVM_L2DepositedToken } from "../../../iOVM/bridge/tokens/iOVM_L2Deposi
/* Library Imports */
import { OVM_CrossDomainEnabled } from "../../../libraries/bridge/OVM_CrossDomainEnabled.sol";
import { Lib_AddressResolver } from "../../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_AddressManager } from "../../../libraries/resolver/Lib_AddressManager.sol";
/**
* @title OVM_L1ETHGateway
......@@ -36,19 +37,31 @@ contract OVM_L1ETHGateway is iOVM_L1ETHGateway, OVM_CrossDomainEnabled, Lib_Addr
* 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 _ovmEth L2 OVM_ETH implementation of iOVM_DepositedToken
*/
constructor(
function initialize(
address _libAddressManager,
address _ovmEth
)
OVM_CrossDomainEnabled(address(0)) // overridden in constructor code
Lib_AddressResolver(_libAddressManager)
public
{
require(libAddressManager == Lib_AddressManager(0), "Contract has already been initialized.");
libAddressManager = Lib_AddressManager(_libAddressManager);
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
assembly {
txDataLength := shr(232, calldataload(nextTransactionPtr))
}
require(
txDataLength <= MAX_ROLLUP_TX_SIZE,
"Transaction data size exceeds maximum for rollup transaction."
);
leaves[leafIndex] = _getSequencerLeafHash(
curContext,
......@@ -937,7 +941,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
internal
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.
if (getTotalElements() > 0) {
(,, uint40 lastTimestamp, uint40 lastBlockNumber) = _getBatchExtraData();
......
......@@ -159,7 +159,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
override
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
// address around in calldata).
ovmStateManager = iOVM_StateManager(_ovmStateManager);
......@@ -206,9 +206,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// Wipe the execution context.
_resetContext();
// Reset the ovmStateManager.
ovmStateManager = iOVM_StateManager(address(0));
}
......@@ -640,7 +637,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
{
// DELEGATECALL does not change anything about the message context.
MessageContext memory nextMessageContext = messageContext;
return _callContract(
nextMessageContext,
_gasLimit,
......@@ -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).
* 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 _gasLimit Amount of gas to be passed into this message.
* @param _contract OVM address being called or deployed to
......@@ -1028,7 +1025,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
* 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 allows for consistent handling of both types of messages in _handleExternalMessage().
*
*
* @param _gasLimit Amount of gas to be passed into this creation.
* @param _creationCode Code to pass into CREATE for deployment.
* @param _address OVM address being deployed to.
......@@ -1075,7 +1072,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// Actually execute the EVM create message,
address ethAddress = Lib_EthUtils.createContract(_creationCode);
if (ethAddress == address(0)) {
// If the creation fails, the EVM lets us grab its revert data. This may contain a revert flag
// to be used above in _handleExternalMessage.
......@@ -1813,6 +1810,9 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
messageContext.isStatic = false;
messageRecord.nuisanceGasLeft = 0;
// Reset the ovmStateManager.
ovmStateManager = iOVM_StateManager(address(0));
}
/*****************************
......@@ -1851,7 +1851,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
if (created == address(0)) {
return (false, revertData);
} 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.
return (true, Lib_EthUtils.getCode(created));
}
......
......@@ -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
* 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.
*
*
* Compiler used: solc
* Runtime target: EVM
*/
......@@ -170,7 +170,7 @@ contract OVM_StateTransitioner is Lib_AddressResolver, Abs_FraudContributor, iOV
{
return phase == TransitionPhase.COMPLETE;
}
/***********************************
* Public Functions: Pre-Execution *
......@@ -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,
// 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).
require(
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
// if that's the case.
ovmExecutionManager.run(_transaction, address(ovmStateManager));
// Prevent the Execution Manager from calling this SM again.
ovmStateManager.setExecutionManager(address(0));
phase = TransitionPhase.POST_EXECUTION;
}
......
......@@ -50,6 +50,7 @@ contract Lib_ResolvedDelegateProxy {
fallback()
external
payable
{
address target = addressManager[address(this)].getAddress((implementationName[address(this)]));
require(
......
......@@ -34,7 +34,7 @@ library Lib_MerkleTree {
"Lib_MerkleTree: Must provide at least one leaf hash."
);
if (_elements.length == 0) {
if (_elements.length == 1) {
return _elements[0];
}
......@@ -203,7 +203,7 @@ library Lib_MerkleTree {
// Borrowed with <3 from https://github.com/ethereum/solidity-examples
uint256 val = _in;
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) {
highest += i;
val >>= i;
......
import { HardhatUserConfig } from 'hardhat/types'
import 'solidity-coverage'
import {
DEFAULT_ACCOUNTS_HARDHAT,
......@@ -17,6 +18,11 @@ const config: HardhatUserConfig = {
accounts: DEFAULT_ACCOUNTS_HARDHAT,
blockGasLimit: RUN_OVM_TEST_GAS * 2,
},
// Add this network to your config!
optimism: {
url: 'http://127.0.0.1:8545',
ovm: true,
},
},
mocha: {
timeout: 50000,
......
......@@ -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:typescript": "tsc -p ./tsconfig.build.json",
"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: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\"",
......@@ -63,6 +63,7 @@
"mocha": "^8.3.0",
"random-bytes-seed": "^1.0.3",
"rlp": "^2.2.6",
"solidity-coverage": "^0.7.16",
"ts-generator": "0.0.8",
"typechain": "2.0.0"
},
......
yarn run build:typescript &
yarn run build:contracts:ovm &
yarn run build:contracts &
# avoid race condition between the 2 concurrent hardhat instances
sleep 2
yarn run build:contracts:ovm &
wait
......
......@@ -109,10 +109,23 @@ export const makeContractDeployConfig = async (
},
OVM_L1ETHGateway: {
factory: getContractFactory('OVM_L1ETHGateway'),
params: [
AddressManager.address,
'0x4200000000000000000000000000000000000006',
],
params: [],
},
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: {
factory: getContractFactory('OVM_L1MultiMessageRelayer'),
......
......@@ -106,7 +106,6 @@ describe('OVM_ECDSAContractAccount', () => {
// The ovmCALL is the 2nd call because the first call transfers the fee.
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._calldata).to.equal(DEFAULT_EIP155_TX.data)
})
......@@ -130,7 +129,6 @@ describe('OVM_ECDSAContractAccount', () => {
// The ovmCALL is the 2nd call because the first call transfers the fee.
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._calldata).to.equal(DEFAULT_EIP155_TX.data)
})
......
......@@ -13,6 +13,7 @@ const L1_ETH_GATEWAY_NAME = 'Proxy__OVM_L1CrossDomainMessenger'
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
const ERR_INVALID_X_DOMAIN_MSG_SENDER =
'OVM_XCHAIN: wrong sender of cross-domain message'
const ERR_ALREADY_INITIALIZED = 'Contract has already been initialized.'
describe('OVM_L1ETHGateway', () => {
// init signers
......@@ -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
)
// Deploy the contract under test
// Deploy the contract under test and initialize
OVM_L1ETHGateway = await (
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()
})
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', () => {
it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L1 account', async () => {
// Deploy new gateway, initialize with random messenger
......@@ -72,7 +88,11 @@ describe('OVM_L1ETHGateway', () => {
OVM_L1ETHGateway = await (
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(
NON_ZERO_ADDRESS
......@@ -147,10 +167,14 @@ describe('OVM_L1ETHGateway', () => {
Mock__OVM_L1CrossDomainMessenger.address
)
// Deploy the contract under test:
// Deploy the contract under test and initialize
OVM_L1ETHGateway = await (
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 () => {
......
......@@ -13,6 +13,7 @@ import {
NON_ZERO_ADDRESS,
getXDomainCalldata,
} from '../../../../helpers'
import { solidityKeccak256 } from 'ethers/lib/utils'
describe('OVM_L2CrossDomainMessenger', () => {
let signer: Signer
......@@ -157,5 +158,44 @@ describe('OVM_L2CrossDomainMessenger', () => {
OVM_L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
).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', () => {
)
})
describe('appendSequencerBatch', () => {
describe('appendSequencerBatch [ @skip-on-coverage ]', () => {
beforeEach(() => {
OVM_CanonicalTransactionChain = OVM_CanonicalTransactionChain.connect(
sequencer
......
......@@ -754,6 +754,32 @@ describe('OVM_CanonicalTransactionChain', () => {
).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', () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
......
......@@ -101,7 +101,7 @@ describe('OVM_ExecutionManager gas consumption', () => {
).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 () => {
const gasCost = await gasMeasurement.getGasCost(
OVM_ExecutionManager,
......
......@@ -21,7 +21,7 @@ const DUMMY_KEY = DUMMY_BYTES32[0]
const DUMMY_VALUE_1 = DUMMY_BYTES32[1]
const DUMMY_VALUE_2 = DUMMY_BYTES32[2]
describe('OVM_StateManager gas consumption', () => {
describe('OVM_StateManager gas consumption [ @skip-on-coverage ]', () => {
let owner: Signer
before(async () => {
;[owner] = await ethers.getSigners()
......
This diff is collapsed.
......@@ -33,6 +33,7 @@
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@ethersproject/abstract-provider": "^5.1.0",
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@types/browser-or-node": "^1.3.0",
"@types/cors": "^2.8.9",
......
......@@ -12,6 +12,8 @@ import {
import {
DecodedSequencerBatchTransaction,
EventArgsSequencerBatchAppended,
SequencerBatchAppendedExtraData,
SequencerBatchAppendedParsedEvent,
TransactionBatchEntry,
TransactionEntry,
EventHandlerSet,
......@@ -21,27 +23,6 @@ import {
SEQUENCER_GAS_LIMIT,
} 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<
EventArgsSequencerBatchAppended,
SequencerBatchAppendedExtraData,
......
......@@ -6,23 +6,16 @@ import { BigNumber } from 'ethers'
import {
EventArgsStateBatchAppended,
StateRootBatchEntry,
StateBatchAppendedExtraData,
StateBatchAppendedParsedEvent,
StateRootEntry,
EventHandlerSet,
} from '../../../types'
export const handleEventsStateBatchAppended: EventHandlerSet<
EventArgsStateBatchAppended,
{
timestamp: number
blockNumber: number
submitter: string
l1TransactionHash: string
l1TransactionData: string
},
{
stateRootBatchEntry: StateRootBatchEntry
stateRootEntries: StateRootEntry[]
}
StateBatchAppendedExtraData,
StateBatchAppendedParsedEvent
> = {
getExtraData: async (event) => {
const eventBlock = await event.getBlock()
......
import { JsonRpcProvider } from '@ethersproject/providers'
import { BigNumber } from 'ethers'
import { TransportDB } from '../db/transport-db'
import { TypedEthersEvent } from './event-types'
import {
TransactionBatchEntry,
TransactionEntry,
StateRootBatchEntry,
StateRootEntry,
} from './database-types'
export type GetExtraDataHandler<TEventArgs, TExtraData> = (
event?: TypedEthersEvent<TEventArgs>,
......@@ -22,3 +30,37 @@ export interface EventHandlerSet<TEventArgs, TExtraData, TParsedEvent> {
parseEvent: ParseEventHandler<TEventArgs, TExtraData, 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 { expect } from '../../../../setup'
import {
SequencerBatchAppendedExtraData,
validateBatchTransaction,
handleEventsSequencerBatchAppended,
} from '../../../../../src/services/l1-ingestion/handlers/sequencer-batch-appended'
import { SequencerBatchAppendedExtraData } from '../../../../../src/types'
import { l1TransactionData } from '../../../examples/l1-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> => {
subtask(
TASK_COMPILE_SOLIDITY_RUN_SOLC,
async (args: { input: any; solcPath: string }, hre, runSuper) => {
if ((hre.network as any).ovm !== true) {
if (hre.network.ovm !== true) {
return runSuper(args)
}
......@@ -127,9 +127,9 @@ subtask(
)
extendEnvironment((hre) => {
if (process.env.TARGET === 'ovm') {
;(hre.network as any).ovm = true
// Quick check to make sure we don't accidentally perform this transform multiple times.
if (hre.network.config.ovm) {
hre.network.ovm = hre.network.config.ovm
let artifactsPath = hre.config.paths.artifacts
if (!artifactsPath.endsWith('-ovm')) {
artifactsPath = artifactsPath + '-ovm'
......@@ -144,6 +144,5 @@ extendEnvironment((hre) => {
hre.config.paths.artifacts = artifactsPath
hre.config.paths.cache = cachePath
;(hre as any).artifacts = new Artifacts(artifactsPath)
;(hre.network as any).ovm = true
}
})
......@@ -12,4 +12,26 @@ declare module 'hardhat/types/config' {
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