Commit f6d8e410 authored by Ben Wilson's avatar Ben Wilson Committed by GitHub

Merge pull request #1909 from ethereum-optimism/develop

Develop -> Master
parents 941a6a6d 5b77e0d3
---
'@eth-optimism/l2geth': patch
---
surface sequencer low-level sequencer execution errors
---
'@eth-optimism/contracts': patch
---
Adds a new TestLib_CrossDomainUtils so we can properly test cross chain encoding functions
---
'@eth-optimism/op-exporter': patch
---
force rebuild
---
'@eth-optimism/batch-submitter': patch
---
adds batchTxBuildTime gauge
......@@ -11,3 +11,7 @@ trim_trailing_whitespace = true
[*.sol]
indent_size = 4
[*.go]
indent_size = 4
indent_style = tab
......@@ -197,7 +197,7 @@ jobs:
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
op-exporter:
name: Publish op-exporter Version ${{ needs.release.outputs.canary-docker-tag }}
name: Publish op-exporter Version ${{ needs.release.outputs.op-exporter}}
needs: release
if: needs.release.outputs.op-exporter != ''
runs-on: ubuntu-latest
......@@ -227,7 +227,7 @@ jobs:
context: .
file: ./ops/docker/Dockerfile.op-exporter
push: true
tags: ethereumoptimism/op-exporter:${{ needs.canary-publish.outputs.op-exporter }}
tags: ethereumoptimism/op-exporter:${{ needs.release.outputs.op-exporter }},ethereumoptimism/op-exporter:latest
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
......
......@@ -77,6 +77,16 @@ const (
staleThreshold = 7
)
var (
// ErrCannotCommitTxn signals that the transaction execution failed
// when attempting to mine a transaction.
//
// NOTE: This error is not expected to occur in regular operation of
// l2geth, rather the actual execution error should be returned to the
// user.
ErrCannotCommitTxn = errors.New("Cannot commit transaction in miner")
)
// environment is the worker's current environment and holds all of the current state information.
type environment struct {
signer types.Signer
......@@ -773,9 +783,13 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres
}
func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool {
return w.commitTransactionsWithError(txs, coinbase, interrupt) != nil
}
func (w *worker) commitTransactionsWithError(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) error {
// Short circuit if current is nil
if w.current == nil {
return true
return ErrCannotCommitTxn
}
if w.current.gasPool == nil {
......@@ -803,7 +817,11 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
inc: true,
}
}
return w.current.tcount == 0 || atomic.LoadInt32(interrupt) == commitInterruptNewHead
if w.current.tcount == 0 ||
atomic.LoadInt32(interrupt) == commitInterruptNewHead {
return ErrCannotCommitTxn
}
return nil
}
// If we don't have enough gas for any further transactions then we're done
if w.current.gasPool.Gas() < params.TxGas {
......@@ -846,7 +864,7 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
case core.ErrNonceTooHigh:
// Reorg notification data race between the transaction pool and miner, skip account =
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce())
txs.Pop()
case nil:
......@@ -861,6 +879,20 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
txs.Shift()
}
// UsingOVM
// Return specific execution errors directly to the user to
// avoid returning the generic ErrCannotCommitTxnErr. It is safe
// to return the error directly since l2geth only processes at
// most one transaction per block. Currently, we map
// ErrNonceTooHigh to ErrNonceTooLow to match the behavior of
// the mempool, but this mapping will be removed at a later
// point once we decided to expose ErrNonceTooHigh to users.
if err == core.ErrNonceTooHigh {
return core.ErrNonceTooLow
} else if err != nil {
return err
}
}
if !w.isRunning() && len(coalescedLogs) > 0 {
......@@ -883,7 +915,10 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
if interrupt != nil {
w.resubmitAdjustCh <- &intervalAdjust{inc: false}
}
return w.current.tcount == 0
if w.current.tcount == 0 {
return ErrCannotCommitTxn
}
return nil
}
// commitNewTx is an OVM addition that mines a block with a single tx in it.
......@@ -945,8 +980,8 @@ func (w *worker) commitNewTx(tx *types.Transaction) error {
acc, _ := types.Sender(w.current.signer, tx)
transactions[acc] = types.Transactions{tx}
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, transactions)
if w.commitTransactions(txs, w.coinbase, nil) {
return errors.New("Cannot commit transaction in miner")
if err := w.commitTransactionsWithError(txs, w.coinbase, nil); err != nil {
return err
}
return w.commit(nil, w.fullTaskHook, tstart)
}
......
......@@ -197,6 +197,7 @@ func TestGenerateBlockAndImportEthash(t *testing.T) {
}
func TestGenerateBlockAndImportClique(t *testing.T) {
t.Skip("OVM breaks this since it aborts after first tx fails")
testGenerateBlockAndImport(t, true)
}
......
......@@ -24,6 +24,7 @@ FROM node as builder
WORKDIR /optimism
COPY .git ./.git
COPY *.json yarn.lock ./
COPY packages/sdk/package.json ./packages/sdk/package.json
COPY packages/core-utils/package.json ./packages/core-utils/package.json
COPY packages/common-ts/package.json ./packages/common-ts/package.json
COPY packages/contracts/package.json ./packages/contracts/package.json
......
......@@ -31,6 +31,7 @@ interface BatchSubmitterMetrics {
batchesSubmitted: Counter<string>
failedSubmissions: Counter<string>
malformedBatches: Counter<string>
batchTxBuildTime: Gauge<string>
}
export abstract class BatchSubmitter {
......@@ -293,6 +294,11 @@ export abstract class BatchSubmitter {
help: 'Count of malformed batches',
registers: [metrics.registry],
}),
batchTxBuildTime: new metrics.client.Gauge({
name: 'batch_tx_build_time',
help: 'Time to construct batch transaction',
registers: [metrics.registry],
}),
}
}
}
......@@ -154,6 +154,9 @@ export class StateBatchSubmitter extends BatchSubmitter {
startBlock: number,
endBlock: number
): Promise<TransactionReceipt> {
const batchTxBuildStart = performance.now()
const batch = await this._generateStateCommitmentBatch(startBlock, endBlock)
const calldata = this.chainContract.interface.encodeFunctionData(
'appendStateBatch',
......@@ -169,6 +172,9 @@ export class StateBatchSubmitter extends BatchSubmitter {
return
}
const batchTxBuildEnd = performance.now()
this.metrics.batchTxBuildTime.set(batchTxBuildEnd - batchTxBuildStart)
const offsetStartsAtIndex = startBlock - this.blockOffset
this.logger.debug('Submitting batch.', { calldata })
......
......@@ -203,6 +203,8 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
return
}
const batchTxBuildStart = performance.now()
const params = await this._generateSequencerBatchParams(
startBlock,
endBlock
......@@ -226,6 +228,10 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
if (!wasBatchTruncated && !this._shouldSubmitBatch(batchSizeInBytes)) {
return
}
const batchTxBuildEnd = performance.now()
this.metrics.batchTxBuildTime.set(batchTxBuildEnd - batchTxBuildStart)
this.metrics.numTxPerBatch.observe(endBlock - startBlock)
const l1tipHeight = await this.signer.provider.getBlockNumber()
this.logger.debug('Submitting batch.', {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
/* Library Imports */
import { Lib_CrossDomainUtils } from "../../libraries/bridge/Lib_CrossDomainUtils.sol";
/**
* @title TestLib_CrossDomainUtils
*/
library TestLib_CrossDomainUtils {
function encodeXDomainCalldata(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
) public pure returns (bytes memory) {
return
Lib_CrossDomainUtils.encodeXDomainCalldata(_target, _sender, _message, _messageNonce);
}
}
import { HardhatUserConfig } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
const config: HardhatUserConfig = {
solidity: {
version: '0.8.9',
},
paths: {
sources: './test/contracts',
},
}
export default config
......@@ -14,7 +14,9 @@
"lint": "yarn lint:fix && yarn lint:check",
"lint:check": "eslint .",
"lint:fix": "yarn lint:check --fix",
"pre-commit": "lint-staged"
"pre-commit": "lint-staged",
"test": "hardhat test",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json"
},
"keywords": [
"optimism",
......
export * from './interfaces'
export * from './utils'
import { Overrides, Contract } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { NumberLike, L1ToL2Overrides } from './types'
import { ICrossChainMessenger } from './cross-chain-messenger'
/**
* Represents an L1<>L2 ERC20 token pair.
*/
export interface ICrossChainERC20Pair {
/**
* Messenger that will be used to carry out cross-chain iteractions.
*/
messenger: ICrossChainMessenger
/**
* Ethers Contract object connected to the L1 token.
*/
l1Token: Contract
/**
* Ethers Contract object connected to the L2 token.
*/
l2Token: Contract
/**
* Deposits some tokens into the L2 chain.
*
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the deposit transaction.
*/
deposit(
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse>
/**
* Withdraws some tokens back to the L1 chain.
*
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the withdraw transaction.
*/
withdraw(
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionResponse>
/**
* Object that holds the functions that generate transactions to be signed by the user.
* Follows the pattern used by ethers.js.
*/
populateTransaction: {
/**
* Generates a transaction for depositing some tokens into the L2 chain.
*
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the tokens.
*/
deposit(
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse>
/**
* Generates a transaction for withdrawing some tokens back to the L1 chain.
*
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdraw(
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
}
/**
* Object that holds the functions that estimates the gas required for a given transaction.
* Follows the pattern used by ethers.js.
*/
estimateGas: {
/**
* Estimates gas required to deposit some tokens into the L2 chain.
*
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the tokens.
*/
deposit(
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse>
/**
* Estimates gas required to withdraw some tokens back to the L1 chain.
*
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdraw(
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
}
}
......@@ -5,7 +5,6 @@ import {
} from '@ethersproject/abstract-provider'
import {
MessageLike,
AddressLike,
NumberLike,
CrossChainMessageRequest,
L1ToL2Overrides,
......@@ -67,20 +66,6 @@ export interface ICrossChainMessenger {
overrides?: Overrides
): Promise<TransactionResponse>
/**
* Deposits some tokens into the L2 chain.
*
* @param token Address of the token to deposit.
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the deposit transaction.
*/
depositTokens(
token: AddressLike,
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse>
/**
* Deposits some ETH into the L2 chain.
*
......@@ -93,20 +78,6 @@ export interface ICrossChainMessenger {
overrides?: L1ToL2Overrides
): Promise<TransactionResponse>
/**
* Withdraws some tokens back to the L1 chain.
*
* @param token Address of the token to withdraw.
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the withdraw transaction.
*/
withdrawTokens(
token: AddressLike,
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionResponse>
/**
* Withdraws some ETH back to the L1 chain.
*
......@@ -166,20 +137,6 @@ export interface ICrossChainMessenger {
overrides?: Overrides
): Promise<TransactionRequest>
/**
* Generates a transaction for depositing some tokens into the L2 chain.
*
* @param token Address of the token to deposit.
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the tokens.
*/
depositTokens(
token: AddressLike,
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse>
/**
* Generates a transaction for depositing some ETH into the L2 chain.
*
......@@ -192,20 +149,6 @@ export interface ICrossChainMessenger {
overrides?: L1ToL2Overrides
): Promise<TransactionRequest>
/**
* Generates a transaction for withdrawing some tokens back to the L1 chain.
*
* @param token Address of the token to withdraw.
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawTokens(
token: AddressLike,
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
/**
* Generates a transaction for withdrawing some ETH back to the L1 chain.
*
......@@ -262,20 +205,6 @@ export interface ICrossChainMessenger {
overrides?: Overrides
): Promise<TransactionRequest>
/**
* Estimates gas required to deposit some tokens into the L2 chain.
*
* @param token Address of the token to deposit.
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the tokens.
*/
depositTokens(
token: AddressLike,
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse>
/**
* Estimates gas required to deposit some ETH into the L2 chain.
*
......@@ -288,20 +217,6 @@ export interface ICrossChainMessenger {
overrides?: L1ToL2Overrides
): Promise<TransactionRequest>
/**
* Estimates gas required to withdraw some tokens back to the L1 chain.
*
* @param token Address of the token to withdraw.
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawTokens(
token: AddressLike,
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
/**
* Estimates gas required to withdraw some ETH back to the L1 chain.
*
......
export * from './cross-chain-erc20-pair'
export * from './cross-chain-messenger'
export * from './cross-chain-provider'
export * from './l2-provider'
......
......@@ -48,6 +48,13 @@ export enum MessageStatus {
*/
UNCONFIRMED_L1_TO_L2_MESSAGE,
/**
* Message is an L1 to L2 message and the transaction to execute the message failed.
* When this status is returned, you will need to resend the L1 to L2 message, probably with a
* higher gas limit.
*/
FAILED_L1_TO_L2_MESSAGE,
/**
* Message is an L2 to L1 message and no state root has been published yet.
*/
......@@ -99,6 +106,14 @@ export interface CrossChainMessage {
messageNonce: number
}
/**
* Convenience type for when you don't care which direction the message is going in.
*/
export type DirectionlessCrossChainMessage = Omit<
CrossChainMessage,
'direction'
>
/**
* Describes a token withdrawal or deposit, along with the underlying raw cross chain message
* behind the deposit or withdrawal.
......@@ -181,7 +196,7 @@ export type MessageLike =
/**
* Stuff that can be coerced into a provider.
*/
export type ProviderLike = string | Provider
export type ProviderLike = string | Provider | any
/**
* Stuff that can be coerced into a signer.
......
import assert from 'assert'
import {
Provider,
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import {
ProviderLike,
TransactionLike,
DirectionlessCrossChainMessage,
} from './interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
* locations within the Optimistic Ethereum smart contracts.
*
* @param message Cross chain message to encode.
* @returns Canonical encoding of the message.
*/
export const encodeCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return getContractInterface('L2CrossDomainMessenger').encodeFunctionData(
'relayMessage',
[message.target, message.sender, message.message, message.messageNonce]
)
}
/**
* Returns the canonical hash of a cross chain message. This hash is used in various locations
* within the Optimistic Ethereum smart contracts and is the keccak256 hash of the result of
* encodeCrossChainMessage.
*
* @param message Cross chain message to hash.
* @returns Canonical hash of the message.
*/
export const hashCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return ethers.utils.solidityKeccak256(
['bytes'],
[encodeCrossChainMessage(message)]
)
}
/**
* Converts a ProviderLike into a provider. Assumes that if the ProviderLike is a string then
* it is a JSON-RPC url.
*
* @param provider ProviderLike to turn into a provider.
* @returns ProviderLike as a provider.
*/
export const toProvider = (provider: ProviderLike): Provider => {
if (typeof provider === 'string') {
return new ethers.providers.JsonRpcProvider(provider)
} else if (Provider.isProvider(provider)) {
return provider
} else {
throw new Error('Invalid provider')
}
}
/**
* Pulls a transaction hash out of a TransactionLike object.
*
* @param transaction TransactionLike to convert into a transaction hash.
* @returns Transaction hash corresponding to the TransactionLike input.
*/
export const toTransactionHash = (transaction: TransactionLike): string => {
if (typeof transaction === 'string') {
assert(
ethers.utils.isHexString(transaction, 32),
'Invalid transaction hash'
)
return transaction
} else if ((transaction as TransactionReceipt).transactionHash) {
return (transaction as TransactionReceipt).transactionHash
} else if ((transaction as TransactionResponse).hash) {
return (transaction as TransactionResponse).hash
} else {
throw new Error('Invalid transaction')
}
}
pragma solidity ^0.8.9;
contract AbsolutelyNothing {
function doAbsolutelyNothing() public {
return;
}
}
pragma solidity ^0.8.9;
contract MessageEncodingHelper {
// This function is copy/pasted from the Lib_CrossDomainUtils library. We have to do this
// because the Lib_CrossDomainUtils library does not provide a function for hashing. Instead,
// I'm duplicating the functionality of the library here and exposing an additional method that
// does the required hashing. This is fragile and will break if we ever update the way that our
// contracts hash the encoded data, but at least it works for now.
// TODO: Next time we're planning to upgrade the contracts, make sure that the library also
// contains a function for hashing.
function encodeXDomainCalldata(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
) public pure returns (bytes memory) {
return
abi.encodeWithSignature(
"relayMessage(address,address,bytes,uint256)",
_target,
_sender,
_message,
_messageNonce
);
}
function hashXDomainCalldata(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
) public pure returns (bytes32) {
return keccak256(
encodeXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
)
);
}
}
/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainERC20Pair', () => {
describe('construction', () => {
it('should have a messenger', () => {})
describe('when only an L1 token is provided', () => {
describe('when the token is a standard bridge token', () => {
it('should resolve an L2 token from the token list', () => {})
})
describe('when the token is ETH', () => {
it('should resolve the L2 ETH token address', () => {})
})
describe('when the token is SNX', () => {
it('should resolve the L2 SNX token address', () => {})
})
describe('when the token is DAI', () => {
it('should resolve the L2 DAI token address', () => {})
})
describe('when the token is not a standard token or a special token', () => {
it('should throw an error', () => {})
})
})
describe('when only an L2 token is provided', () => {
describe('when the token is a standard bridge token', () => {
it('should resolve an L1 token from the token list', () => {})
})
describe('when the token is ETH', () => {
it('should resolve the L1 ETH token address', () => {})
})
describe('when the token is SNX', () => {
it('should resolve the L1 SNX token address', () => {})
})
describe('when the token is DAI', () => {
it('should resolve the L1 DAI token address', () => {})
})
describe('when the token is not a standard token or a special token', () => {
it('should throw an error', () => {})
})
})
describe('when both an L1 token and an L2 token are provided', () => {
it('should attach both instances', () => {})
})
describe('when neither an L1 token or an L2 token are provided', () => {
it('should throw an error', () => {})
})
})
describe('deposit', () => {
describe('when the user has enough balance and allowance', () => {
describe('when the token is a standard bridge token', () => {
it('should trigger a token deposit', () => {})
})
describe('when the token is ETH', () => {
it('should trigger a token deposit', () => {})
})
describe('when the token is SNX', () => {
it('should trigger a token deposit', () => {})
})
describe('when the token is DAI', () => {
it('should trigger a token deposit', () => {})
})
})
describe('when the user does not have enough balance', () => {
it('should throw an error', () => {})
})
describe('when the user has not given enough allowance to the bridge', () => {
it('should throw an error', () => {})
})
})
describe('withdraw', () => {
describe('when the user has enough balance', () => {
describe('when the token is a standard bridge token', () => {
it('should trigger a token withdrawal', () => {})
})
describe('when the token is ETH', () => {
it('should trigger a token withdrawal', () => {})
})
describe('when the token is SNX', () => {
it('should trigger a token withdrawal', () => {})
})
describe('when the token is DAI', () => {
it('should trigger a token withdrawal', () => {})
})
})
describe('when the user does not have enough balance', () => {
it('should throw an error', () => {})
})
})
describe('populateTransaction', () => {
describe('deposit', () => {
it('should populate the transaction with the correct values', () => {})
})
describe('withdraw', () => {
it('should populate the transaction with the correct values', () => {})
})
})
describe('estimateGas', () => {
describe('estimateGas', () => {
it('should estimate gas required for the transaction', () => {})
})
describe('estimateGas', () => {
it('should estimate gas required for the transaction', () => {})
})
})
})
/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainMessenger', () => {
describe('sendMessage', () => {
describe('when no l2GasLimit is provided', () => {
it('should send a message with an estimated l2GasLimit', () => {})
})
describe('when an l2GasLimit is provided', () => {
it('should send a message with the provided l2GasLimit', () => {})
})
})
describe('resendMessage', () => {
describe('when the message being resent exists', () => {
it('should resend the message with the new gas limit', () => {})
})
describe('when the message being resent does not exist', () => {
it('should throw an error', () => {})
})
})
describe('finalizeMessage', () => {
describe('when the message being finalized exists', () => {
describe('when the message is ready to be finalized', () => {
it('should finalize the message', () => {})
})
describe('when the message is not ready to be finalized', () => {
it('should throw an error', () => {})
})
// TODO: is this the behavior we want?
describe('when the message has already been finalized', () => {
it('should throw an error', () => {})
})
})
describe('when the message being finalized does not exist', () => {
it('should throw an error', () => {})
})
})
})
/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainProvider', () => {
describe('construction', () => {
describe('basic construction (given L1 and L2 providers)', () => {
it('should have an l1Provider', () => {})
it('should have an l2Provider', () => {})
it('should have an l1ChainId', () => {})
it('should have an l2ChainId', () => {})
it('should have all contract connections', () => {})
})
})
describe('getMessagesByTransaction', () => {
describe('when a direction is specified', () => {
describe('when the transaction exists', () => {
describe('when thetransaction has messages', () => {
for (const n of [1, 2, 4, 8]) {
it(`should find ${n} messages when the transaction emits ${n} messages`, () => {})
}
})
describe('when the transaction has no messages', () => {
it('should find nothing', () => {})
})
})
describe('when the transaction does not exist', () => {
it('should throw an error', () => {})
})
})
describe('when a direction is not specified', () => {
describe('when the transaction exists only on L1', () => {
describe('when the transaction has messages', () => {
for (const n of [1, 2, 4, 8]) {
it(`should find ${n} messages when the transaction emits ${n} messages`, () => {})
}
})
describe('when the transaction has no messages', () => {
it('should find nothing', () => {})
})
})
describe('when the transaction exists only on L2', () => {
describe('when the transaction has messages', () => {
for (const n of [1, 2, 4, 8]) {
it(`should find ${n} messages when the transaction emits ${n} messages`, () => {})
}
})
describe('when the transaction has no messages', () => {
it('should find nothing', () => {})
})
})
describe('when the transaction does not exist', () => {
it('should throw an error', () => {})
})
describe('when the transaction exists on both L1 and L2', () => {
it('should throw an error', () => {})
})
})
})
describe('getMessagesByAddress', () => {
describe('when the address has sent messages', () => {
describe('when no direction is specified', () => {
it('should find all messages sent by the address', () => {})
})
describe('when a direction is specified', () => {
it('should find all messages only in the given direction', () => {})
})
describe('when a block range is specified', () => {
it('should find all messages within the block range', () => {})
})
describe('when both a direction and a block range are specified', () => {
it('should find all messages only in the given direction and within the block range', () => {})
})
})
describe('when the address has not sent messages', () => {
it('should find nothing', () => {})
})
})
describe('getTokenBridgeMessagesByAddress', () => {
describe('when the address has made deposits or withdrawals', () => {
describe('when a direction of L1 => L2 is specified', () => {
it('should find all deposits made by the address', () => {})
})
describe('when a direction of L2 => L1 is specified', () => {
it('should find all withdrawals made by the address', () => {})
})
describe('when a block range is specified', () => {
it('should find all deposits or withdrawals within the block range', () => {})
})
describe('when both a direction and a block range are specified', () => {
it('should find all deposits or withdrawals only in the given direction and within the block range', () => {})
})
})
describe('when the address has not made any deposits or withdrawals', () => {
it('should find nothing', () => {})
})
})
describe('getMessageStatus', () => {
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE', () => {})
})
describe('when the message has been executed on L2', () => {
it('should return a status of RELAYED', () => {})
})
describe('when the message has been executed but failed', () => {
it('should return a status of FAILED_L1_TO_L2_MESSAGE', () => {})
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the message state root has not been published', () => {
it('should return a status of STATE_ROOT_NOT_PUBLISHED', () => {})
})
describe('when the message state root is still in the challenge period', () => {
it('should return a status of IN_CHALLENGE_PERIOD', () => {})
})
describe('when the message is no longer in the challenge period', () => {
describe('when the message has been relayed successfully', () => {
it('should return a status of RELAYED', () => {})
})
describe('when the message has been relayed but the relay failed', () => {
it('should return a status of READY_FOR_RELAY', () => {})
})
describe('when the message has not been relayed', () => {
it('should return a status of READY_FOR_RELAY')
})
})
})
})
describe('getMessageReceipt', () => {
describe('when the message has been relayed', () => {
describe('when the relay was successful', () => {
it('should return the receipt of the transaction that relayed the message', () => {})
})
describe('when the relay failed', () => {
it('should return the receipt of the transaction that attempted to relay the message', () => {})
})
describe('when the relay failed more than once', () => {
it('should return the receipt of the last transaction that attempted to relay the message', () => {})
})
})
describe('when the message has not been relayed', () => {
it('should return null', () => {})
})
})
describe('waitForMessageReciept', () => {
describe('when the message receipt already exists', () => {
it('should immediately return the receipt', () => {})
})
describe('when the message receipt does not exist already', () => {
describe('when no extra options are provided', () => {
it('should wait for the receipt to be published', () => {})
it('should wait forever for the receipt if the receipt is never published', () => {})
})
describe('when a timeout is provided', () => {
it('should throw an error if the timeout is reached', () => {})
})
})
})
describe('estimateMessageExecutionGas', () => {
describe('when the message is an L1 => L2 message', () => {
it('should perform a gas estimation of the L2 action', () => {})
})
describe('when the message is an L2 => L1 message', () => {
it('should perform a gas estimation of the L1 action, including the cost of the proof', () => {})
})
})
describe('estimateMessageWaitTimeBlocks', () => {
describe('when the message exists', () => {
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
it('should return the estimated blocks until the message will be confirmed on L2', () => {})
})
describe('when the message has been executed on L2', () => {
it('should return 0', () => {})
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the state root has not been published', () => {
it('should return null', () => {})
})
describe('when the state root is within the challenge period', () => {
it('should return the estimated blocks until the state root passes the challenge period', () => {})
})
describe('when the state root passes the challenge period', () => {
it('should return 0', () => {})
})
})
})
describe('when the message does not exist', () => {
it('should throw an error', () => {})
})
})
describe('estimateMessageWaitTimeSeconds', () => {
it('should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time', () => {})
})
})
import { expect } from './setup'
import { Provider } from '@ethersproject/abstract-provider'
import { Contract, Signer } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import {
toProvider,
toTransactionHash,
CrossChainMessage,
MessageDirection,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../src'
describe('utils', () => {
let signers: Signer[]
before(async () => {
signers = (await ethers.getSigners()) as any
})
describe('encodeCrossChainMessage', () => {
let Lib_CrossDomainUtils: Contract
before(async () => {
Lib_CrossDomainUtils = (await getContractFactory(
'TestLib_CrossDomainUtils',
signers[0]
).deploy()) as any
})
it('should properly encode a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = encodeCrossChainMessage(message)
const expected = await Lib_CrossDomainUtils.encodeXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('hashCrossChainMessage', () => {
let MessageEncodingHelper: Contract
before(async () => {
MessageEncodingHelper = (await (
await ethers.getContractFactory('MessageEncodingHelper')
).deploy()) as any
})
it('should properly hash a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = hashCrossChainMessage(message)
const expected = await MessageEncodingHelper.hashXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('toProvider', () => {
it('should convert a string to a JsonRpcProvider', () => {
const provider = toProvider('http://localhost:8545')
expect(Provider.isProvider(provider)).to.be.true
})
it('should not do anything with a provider', () => {
const provider = toProvider(ethers.provider)
expect(provider).to.deep.equal(ethers.provider)
})
})
describe('toTransactionHash', () => {
describe('string inputs', () => {
it('should return the input if the input is a valid transaction hash', () => {
const input = '0x' + '11'.repeat(32)
expect(toTransactionHash(input)).to.equal(input)
})
it('should throw an error if the input is a hex string but not a transaction hash', () => {
const input = '0x' + '11'.repeat(31)
expect(() => toTransactionHash(input)).to.throw(
'Invalid transaction hash'
)
})
it('should throw an error if the input is not a hex string', () => {
const input = 'hi mom look at me go'
expect(() => toTransactionHash(input)).to.throw(
'Invalid transaction hash'
)
})
})
describe('transaction inputs', () => {
let AbsolutelyNothing: Contract
before(async () => {
AbsolutelyNothing = (await (
await ethers.getContractFactory('AbsolutelyNothing')
).deploy()) as any
})
it('should return the transaction hash if the input is a transaction response', async () => {
const tx = await AbsolutelyNothing.doAbsolutelyNothing()
expect(toTransactionHash(tx)).to.equal(tx.hash)
})
it('should return the transaction hash if the input is a transaction receipt', async () => {
const tx = await AbsolutelyNothing.doAbsolutelyNothing()
const receipt = await tx.wait()
expect(toTransactionHash(receipt)).to.equal(receipt.transactionHash)
})
})
describe('other types', () => {
it('should throw if given a number as an input', () => {
expect(() => toTransactionHash(1234 as any)).to.throw(
'Invalid transaction'
)
})
it('should throw if given a function as an input', () => {
expect(() =>
toTransactionHash((() => {
return 1234
}) as any)
).to.throw('Invalid transaction')
})
})
})
})
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