Commit 5968bada authored by smartcontracts's avatar smartcontracts Committed by GitHub

Merge pull request #1939 from ethereum-optimism/develop

Develop -> Master
parents 8e9834b3 654a4907
---
'@eth-optimism/proxyd': patch
---
Force proxyd build
---
'@eth-optimism/l2geth': patch
---
expose ErrNonceTooHigh from miner
......@@ -119,8 +119,6 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
log.Root().SetHandler(log.LvlFilterHandler(logLevel, logHandler))
log.Info("Config", "config", fmt.Sprintf("%#v", cfg))
// Parse sequencer private key and CTC contract address.
sequencerPrivKey, ctcAddress, err := parseWalletPrivKeyAndContractAddr(
"Sequencer", cfg.Mnemonic, cfg.SequencerHDPath,
......@@ -171,7 +169,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
var batchTxService *Service
if cfg.RunTxBatchSubmitter {
batchTxDriver, err := sequencer.NewDriver(sequencer.Config{
Name: "SEQUENCER",
Name: "Sequencer",
L1Client: l1Client,
L2Client: l2Client,
BlockOffset: cfg.BlockOffset,
......@@ -196,7 +194,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
var batchStateService *Service
if cfg.RunStateBatchSubmitter {
batchStateDriver, err := proposer.NewDriver(proposer.Config{
Name: "PROPOSER",
Name: "Proposer",
L1Client: l1Client,
L2Client: l2Client,
BlockOffset: cfg.BlockOffset,
......
......@@ -5,9 +5,11 @@ import (
"crypto/ecdsa"
"fmt"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/scc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
......@@ -36,6 +38,7 @@ type Driver struct {
sccContract *scc.StateCommitmentChain
ctcContract *ctc.CanonicalTransactionChain
walletAddr common.Address
metrics *metrics.Metrics
}
func NewDriver(cfg Config) (*Driver, error) {
......@@ -60,6 +63,7 @@ func NewDriver(cfg Config) (*Driver, error) {
sccContract: sccContract,
ctcContract: ctcContract,
walletAddr: walletAddr,
metrics: metrics.NewMetrics(cfg.Name),
}, nil
}
......@@ -73,6 +77,11 @@ func (d *Driver) WalletAddr() common.Address {
return d.walletAddr
}
// Metrics returns the subservice telemetry object.
func (d *Driver) Metrics() *metrics.Metrics {
return d.metrics
}
// GetBatchBlockRange returns the start and end L2 block heights that need to be
// processed. Note that the end value is *exclusive*, therefore if the returned
// values are identical nothing needs to be processed.
......@@ -121,6 +130,8 @@ func (d *Driver) SubmitBatchTx(
ctx context.Context,
start, end, nonce, gasPrice *big.Int) (*types.Transaction, error) {
batchTxBuildStart := time.Now()
var blocks []*l2types.Block
for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) {
block, err := d.cfg.L2Client.BlockByNumber(ctx, i)
......@@ -139,6 +150,10 @@ func (d *Driver) SubmitBatchTx(
stateRoots = append(stateRoots, block.Root())
}
batchTxBuildTime := float64(time.Since(batchTxBuildStart) / time.Millisecond)
d.metrics.BatchTxBuildTime.Set(batchTxBuildTime)
d.metrics.NumTxPerBatch.Observe(float64(len(blocks)))
opts, err := bind.NewKeyedTransactorWithChainID(
d.cfg.PrivKey, d.cfg.ChainID,
)
......
......@@ -7,8 +7,10 @@ import (
"fmt"
"math/big"
"strings"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi"
......@@ -43,6 +45,7 @@ type Driver struct {
rawCtcContract *bind.BoundContract
walletAddr common.Address
ctcABI *abi.ABI
metrics *metrics.Metrics
}
func NewDriver(cfg Config) (*Driver, error) {
......@@ -78,6 +81,7 @@ func NewDriver(cfg Config) (*Driver, error) {
rawCtcContract: rawCtcContract,
walletAddr: walletAddr,
ctcABI: ctcABI,
metrics: metrics.NewMetrics(cfg.Name),
}, nil
}
......@@ -91,6 +95,11 @@ func (d *Driver) WalletAddr() common.Address {
return d.walletAddr
}
// Metrics returns the subservice telemetry object.
func (d *Driver) Metrics() *metrics.Metrics {
return d.metrics
}
// GetBatchBlockRange returns the start and end L2 block heights that need to be
// processed. Note that the end value is *exclusive*, therefore if the returned
// values are identical nothing needs to be processed.
......@@ -136,6 +145,8 @@ func (d *Driver) SubmitBatchTx(
log.Info(name+" submitting batch tx", "start", start, "end", end,
"gasPrice", gasPrice)
batchTxBuildStart := time.Now()
var blocks []*l2types.Block
for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) {
block, err := d.cfg.L2Client.BlockByNumber(ctx, i)
......@@ -176,6 +187,11 @@ func (d *Driver) SubmitBatchTx(
panic("call data too large")
}
// Record the batch_tx_build_time.
batchTxBuildTime := float64(time.Since(batchTxBuildStart) / time.Millisecond)
d.metrics.BatchTxBuildTime.Set(batchTxBuildTime)
d.metrics.NumTxPerBatch.Observe(float64(len(blocks)))
log.Info(name+" batch call data", "data", hex.EncodeToString(batchCallData))
opts, err := bind.NewKeyedTransactorWithChainID(
......
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type Metrics struct {
// ETHBalance tracks the amount of ETH in the submitter's account.
ETHBalance prometheus.Gauge
// BatchSizeInBytes tracks the size of batch submission transactions.
BatchSizeInBytes prometheus.Histogram
// NumTxPerBatch tracks the number of L2 transactions in each batch
// submission.
NumTxPerBatch prometheus.Histogram
// SubmissionGasUsed tracks the amount of gas used to submit each batch.
SubmissionGasUsed prometheus.Histogram
// BatchsSubmitted tracks the total number of successful batch submissions.
BatchesSubmitted prometheus.Counter
// FailedSubmissions tracks the total number of failed batch submissions.
FailedSubmissions prometheus.Counter
// BatchTxBuildTime tracks the duration it takes to construct a batch
// transaction.
BatchTxBuildTime prometheus.Gauge
// BatchConfirmationTime tracks the duration it takes to confirm a batch
// transaction.
BatchConfirmationTime prometheus.Gauge
}
func NewMetrics(subsystem string) *Metrics {
return &Metrics{
ETHBalance: promauto.NewGauge(prometheus.GaugeOpts{
Name: "batch_submitter_eth_balance",
Help: "ETH balance of the batch submitter",
Subsystem: subsystem,
}),
BatchSizeInBytes: promauto.NewHistogram(prometheus.HistogramOpts{
Name: "batch_submitter_batch_size_in_bytes",
Help: "Size of batches in bytes",
Subsystem: subsystem,
}),
NumTxPerBatch: promauto.NewHistogram(prometheus.HistogramOpts{
Name: "batch_submitter_num_txs_per_batch",
Help: "Number of transaction in each batch",
Subsystem: subsystem,
}),
SubmissionGasUsed: promauto.NewHistogram(prometheus.HistogramOpts{
Name: "batch_submitter_submission_gas_used",
Help: "Gas used to submit each batch",
Subsystem: subsystem,
}),
BatchesSubmitted: promauto.NewCounter(prometheus.CounterOpts{
Name: "batch_submitter_batches_submitted",
Help: "Count of batches submitted",
Subsystem: subsystem,
}),
FailedSubmissions: promauto.NewCounter(prometheus.CounterOpts{
Name: "batch_submitter_failed_submissions",
Help: "Count of failed batch submissions",
Subsystem: subsystem,
}),
BatchTxBuildTime: promauto.NewGauge(prometheus.GaugeOpts{
Name: "batch_submitter_batch_tx_build_time",
Help: "Time to construct batch transactions",
Subsystem: subsystem,
}),
BatchConfirmationTime: promauto.NewGauge(prometheus.GaugeOpts{
Name: "batch_submitter_batch_confirmation_time",
Help: "Time to confirm batch transactions",
Subsystem: subsystem,
}),
}
}
......@@ -6,6 +6,7 @@ import (
"sync"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
......@@ -13,6 +14,11 @@ import (
"github.com/ethereum/go-ethereum/log"
)
var (
// weiToGwei is the conversion rate from wei to gwei.
weiToGwei = new(big.Float).SetFloat64(1e-18)
)
// Driver is an interface for creating and submitting batch transactions for a
// specific contract.
type Driver interface {
......@@ -23,6 +29,9 @@ type Driver interface {
// fees.
WalletAddr() common.Address
// Metrics returns the subservice telemetry object.
Metrics() *metrics.Metrics
// GetBatchBlockRange returns the start and end L2 block heights that
// need to be processed. Note that the end value is *exclusive*,
// therefore if the returned values are identical nothing needs to be
......@@ -51,7 +60,8 @@ type Service struct {
ctx context.Context
cancel func()
txMgr txmgr.TxManager
txMgr txmgr.TxManager
metrics *metrics.Metrics
wg sync.WaitGroup
}
......@@ -64,10 +74,11 @@ func NewService(cfg ServiceConfig) *Service {
)
return &Service{
cfg: cfg,
ctx: ctx,
cancel: cancel,
txMgr: txMgr,
cfg: cfg,
ctx: ctx,
cancel: cancel,
txMgr: txMgr,
metrics: cfg.Driver.Metrics(),
}
}
......@@ -91,21 +102,35 @@ func (s *Service) eventLoop() {
for {
select {
case <-time.After(s.cfg.PollInterval):
log.Info(name + " fetching current block range")
// Record the submitter's current ETH balance. This is done first in
// case any of the remaining steps fail, we can at least have an
// accurate view of the submitter's balance.
balance, err := s.cfg.L1Client.BalanceAt(
s.ctx, s.cfg.Driver.WalletAddr(), nil,
)
if err != nil {
log.Error(name+" unable to get current balance", "err", err)
continue
}
s.metrics.ETHBalance.Set(weiToGwei64(balance))
// Determine the range of L2 blocks that the batch submitter has not
// processed, and needs to take action on.
log.Info(name + " fetching current block range")
start, end, err := s.cfg.Driver.GetBatchBlockRange(s.ctx)
if err != nil {
log.Error(name+" unable to get block range", "err", err)
continue
}
log.Info(name+" block range", "start", start, "end", end)
// No new updates.
if start.Cmp(end) == 0 {
log.Info(name+" no updates", "start", start, "end", end)
continue
}
log.Info(name+" block range", "start", start, "end", end)
// Query for the submitter's current nonce.
nonce64, err := s.cfg.L1Client.NonceAt(
s.ctx, s.cfg.Driver.WalletAddr(), nil,
)
......@@ -116,6 +141,8 @@ func (s *Service) eventLoop() {
}
nonce := new(big.Int).SetUint64(nonce64)
// Construct the transaction submission clousure that will attempt
// to send the next transaction at the given nonce and gas price.
sendTx := func(
ctx context.Context,
gasPrice *big.Int,
......@@ -123,20 +150,38 @@ func (s *Service) eventLoop() {
log.Info(name+" attempting batch tx", "start", start,
"end", end, "nonce", nonce,
"gasPrice", gasPrice)
return s.cfg.Driver.SubmitBatchTx(
tx, err := s.cfg.Driver.SubmitBatchTx(
ctx, start, end, nonce, gasPrice,
)
if err != nil {
return nil, err
}
s.metrics.BatchSizeInBytes.Observe(float64(tx.Size()))
return tx, nil
}
// Wait until one of our submitted transactions confirms. If no
// receipt is received it's likely our gas price was too low.
batchConfirmationStart := time.Now()
receipt, err := s.txMgr.Send(s.ctx, sendTx)
if err != nil {
log.Error(name+" unable to publish batch tx",
"err", err)
s.metrics.FailedSubmissions.Inc()
continue
}
// The transaction was successfully submitted.
log.Info(name+" batch tx successfully published",
"tx_hash", receipt.TxHash)
batchConfirmationTime := time.Since(batchConfirmationStart) /
time.Millisecond
s.metrics.BatchConfirmationTime.Set(float64(batchConfirmationTime))
s.metrics.BatchesSubmitted.Inc()
s.metrics.SubmissionGasUsed.Observe(float64(receipt.GasUsed))
case err := <-s.ctx.Done():
log.Error(name+" service shutting down", "err", err)
......@@ -144,3 +189,10 @@ func (s *Service) eventLoop() {
}
}
}
func weiToGwei64(wei *big.Int) float64 {
gwei := new(big.Float).SetInt(wei)
gwei.Mul(gwei, weiToGwei)
gwei64, _ := gwei.Float64()
return gwei64
}
# Changelog
## 0.5.5
### Patch Changes
- 2924845d: expose ErrNonceTooHigh from miner
## 0.5.4
### Patch Changes
......
{
"name": "@eth-optimism/l2geth",
"version": "0.5.4",
"version": "0.5.5",
"private": true,
"devDependencies": {}
}
......@@ -33,11 +33,6 @@ export interface ICrossChainProvider {
*/
l1ChainId: number
/**
* Chain ID for the L2 network.
*/
l2ChainId: number
/**
* Contract objects attached to their respective providers and addresses.
*/
......@@ -129,9 +124,9 @@ export interface ICrossChainProvider {
*
* @param message Message to wait for.
* @param opts Options to pass to the waiting function.
* - `confirmations` (number): Number of transaction confirmations to wait for before returning.
* - `pollIntervalMs` (number): Number of milliseconds to wait between polling for the receipt.
* - `loopsBeforeTimeout` (number): Number of times to poll before timing out.
* @param opts.confirmations Number of transaction confirmations to wait for before returning.
* @param opts.pollIntervalMs Number of milliseconds to wait between polling for the receipt.
* @param opts.timeoutMs Milliseconds to wait before timing out.
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
......@@ -140,7 +135,7 @@ export interface ICrossChainProvider {
opts?: {
confirmations?: number
pollIntervalMs?: number
loopsBeforeTimeout?: number
timeoutMs?: number
}
): Promise<MessageReceipt>
......
......@@ -6,37 +6,65 @@ import {
import { Signer } from '@ethersproject/abstract-signer'
import { Contract, BigNumber, Overrides } from 'ethers'
/**
* L1 contract references.
*/
export interface OEL1Contracts {
AddressManager: Contract
L1CrossDomainMessenger: Contract
L1StandardBridge: Contract
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
BondManager: Contract
}
/**
* L2 contract references.
*/
export interface OEL2Contracts {
L2CrossDomainMessenger: Contract
L2StandardBridge: Contract
OVM_L1BlockNumber: Contract
OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract
OVM_ETH: Contract
OVM_GasPriceOracle: Contract
OVM_SequencerFeeVault: Contract
WETH: Contract
}
/**
* Represents Optimistic Ethereum contracts, assumed to be connected to their appropriate
* providers and addresses.
*/
export interface OEContracts {
/**
* L1 contract references.
*/
l1: {
AddressManager: Contract
L1CrossDomainMessenger: Contract
L1StandardBridge: Contract
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
BondManager: Contract
}
l1: OEL1Contracts
l2: OEL2Contracts
}
/**
* L2 contract references.
*/
l2: {
L2CrossDomainMessenger: Contract
L2StandardBridge: Contract
OVM_L1BlockNumber: Contract
OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract
OVM_ETH: Contract
OVM_GasPriceOracle: Contract
OVM_SequencerFeeVault: Contract
WETH: Contract
}
/**
* Convenience type for something that looks like the L1 OE contract interface but could be
* addresses instead of actual contract objects.
*/
export type OEL1ContractsLike = {
[K in keyof OEL1Contracts]: AddressLike
}
/**
* Convenience type for something that looks like the L2 OE contract interface but could be
* addresses instead of actual contract objects.
*/
export type OEL2ContractsLike = {
[K in keyof OEL2Contracts]: AddressLike
}
/**
* Convenience type for something that looks like the OE contract interface but could be
* addresses instead of actual contract objects.
*/
export interface OEContractsLike {
l1: OEL1ContractsLike
l2: OEL2ContractsLike
}
/**
......@@ -164,13 +192,6 @@ export interface StateRootBatch {
stateRoots: string[]
}
/**
* Utility type for deep partials.
*/
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>
}
/**
* Extended Ethers overrides object with an l2GasLimit field.
* Only meant to be used for L1 to L2 messages, since L2 to L1 messages don't have a specified gas
......
......@@ -4,46 +4,13 @@ import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { ethers, BigNumber } 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)]
)
}
NumberLike,
AddressLike,
} from '../interfaces'
/**
* Converts a ProviderLike into a provider. Assumes that if the ProviderLike is a string then
......@@ -84,3 +51,29 @@ export const toTransactionHash = (transaction: TransactionLike): string => {
throw new Error('Invalid transaction')
}
}
/**
* Converts a number-like into an ethers BigNumber.
*
* @param num Number-like to convert into a BigNumber.
* @returns Number-like as a BigNumber.
*/
export const toBigNumber = (num: NumberLike): BigNumber => {
return ethers.BigNumber.from(num)
}
/**
* Converts an address-like into a 0x-prefixed address string.
*
* @param addr Address-like to convert into an address.
* @returns Address-like as an address.
*/
export const toAddress = (addr: AddressLike): string => {
if (typeof addr === 'string') {
assert(ethers.utils.isAddress(addr), 'Invalid address')
return ethers.utils.getAddress(addr)
} else {
assert(ethers.utils.isAddress(addr.address), 'Invalid address')
return ethers.utils.getAddress(addr.address)
}
}
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { ethers, Contract } from 'ethers'
import {
OEContracts,
OEL1Contracts,
OEL2Contracts,
OEContractsLike,
OEL2ContractsLike,
AddressLike,
} from '../interfaces'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
/**
* Full list of default L2 contract addresses.
*/
export const DEFAULT_L2_CONTRACT_ADDRESSES: OEL2ContractsLike = {
L2CrossDomainMessenger: predeploys.L2CrossDomainMessenger,
L2StandardBridge: predeploys.L2StandardBridge,
OVM_L1BlockNumber: predeploys.OVM_L1BlockNumber,
OVM_L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser,
OVM_DeployerWhitelist: predeploys.OVM_DeployerWhitelist,
OVM_ETH: predeploys.OVM_ETH,
OVM_GasPriceOracle: predeploys.OVM_GasPriceOracle,
OVM_SequencerFeeVault: predeploys.OVM_SequencerFeeVault,
WETH: predeploys.WETH9,
}
/**
* We've changed some contract names in this SDK to be a bit nicer. Here we remap these nicer names
* back to the original contract names so we can look them up.
*/
const NAME_REMAPPING = {
AddressManager: 'Lib_AddressManager',
OVM_L1BlockNumber: 'iOVM_L1BlockNumber',
WETH: 'WETH9',
}
/**
* Mapping of L1 chain IDs to the appropriate contract addresses for the OE deployments to the
* given network. Simplifies the process of getting the correct contract addresses for a given
* contract name.
*/
export const CONTRACT_ADDRESSES: {
[l1ChainId: number]: OEContractsLike
} = {
// Mainnet
1: {
l1: {
AddressManager: '0xdE1FCfB0851916CA5101820A69b13a4E276bd81F',
L1CrossDomainMessenger: '0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1',
L1StandardBridge: '0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1',
StateCommitmentChain: '0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19',
CanonicalTransactionChain: '0x5E4e65926BA27467555EB562121fac00D24E9dD2',
BondManager: '0xcd626E1328b41fCF24737F137BcD4CE0c32bc8d1',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
// Kovan
42: {
l1: {
AddressManager: '0x100Dd3b414Df5BbA2B542864fF94aF8024aFdf3a',
L1CrossDomainMessenger: '0x4361d0F75A0186C05f971c566dC6bEa5957483fD',
L1StandardBridge: '0x22F24361D548e5FaAfb36d1437839f080363982B',
StateCommitmentChain: '0xD7754711773489F31A0602635f3F167826ce53C5',
CanonicalTransactionChain: '0xf7B88A133202d41Fe5E2Ab22e6309a1A4D50AF74',
BondManager: '0xc5a603d273E28185c18Ba4d26A0024B2d2F42740',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
// Goerli
5: {
l1: {
AddressManager: '0x2F7E3cAC91b5148d336BbffB224B4dC79F09f01D',
L1CrossDomainMessenger: '0xEcC89b9EDD804850C4F343A278Be902be11AaF42',
L1StandardBridge: '0x73298186A143a54c20ae98EEE5a025bD5979De02',
StateCommitmentChain: '0x1afcA918eff169eE20fF8AB6Be75f3E872eE1C1A',
CanonicalTransactionChain: '0x2ebA8c4EfDB39A8Cd8f9eD65c50ec079f7CEBD81',
BondManager: '0xE5AE60bD6F8DEe4D0c2BC9268e23B92F1cacC58F',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
}
/**
* Returns an ethers.Contract object for the given name, connected to the appropriate address for
* the given L1 chain ID. Users can also provide a custom address to connect the contract to
* instead. If the chain ID is not known then the user MUST provide a custom address or this
* function will throw an error.
*
* @param contractName Name of the contract to connect to.
* @param l1ChainId Chain ID for the L1 network where the OE contracts are deployed.
* @param opts Additional options for connecting to the contract.
* @param opts.address Custom address to connect to the contract.
* @param opts.signerOrProvider Signer or provider to connect to the contract.
* @returns An ethers.Contract object connected to the appropriate address and interface.
*/
export const getOEContract = (
contractName: keyof OEL1Contracts | keyof OEL2Contracts,
l1ChainId: number,
opts: {
address?: AddressLike
signerOrProvider?: ethers.Signer | ethers.providers.Provider
} = {}
): Contract => {
const addresses = CONTRACT_ADDRESSES[l1ChainId]
if (addresses === undefined && opts.address === undefined) {
throw new Error(
`cannot get contract ${contractName} for unknown L1 chain ID ${l1ChainId}, you must provide an address`
)
}
return new Contract(
toAddress(
opts.address || addresses.l1[contractName] || addresses.l2[contractName]
),
getContractInterface(NAME_REMAPPING[contractName] || contractName),
opts.signerOrProvider
)
}
/**
* Automatically connects to all contract addresses, both L1 and L2, for the given L1 chain ID. The
* user can provide custom contract address overrides for L1 or L2 contracts. If the given chain ID
* is not known then the user MUST provide custom contract addresses for ALL L1 contracts or this
* function will throw an error.
*
* @param l1ChainId Chain ID for the L1 network where the OE contracts are deployed.
* @param opts Additional options for connecting to the contracts.
* @param opts.l1SignerOrProvider: Signer or provider to connect to the L1 contracts.
* @param opts.l2SignerOrProvider: Signer or provider to connect to the L2 contracts.
* @param opts.overrides Custom contract address overrides for L1 or L2 contracts.
* @returns An object containing ethers.Contract objects connected to the appropriate addresses on
* both L1 and L2.
*/
export const getAllOEContracts = (
l1ChainId: number,
opts: {
l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
overrides?: DeepPartial<OEContractsLike>
} = {}
): OEContracts => {
const addresses = CONTRACT_ADDRESSES[l1ChainId] || {
l1: {
AddressManager: undefined,
L1CrossDomainMessenger: undefined,
L1StandardBridge: undefined,
StateCommitmentChain: undefined,
CanonicalTransactionChain: undefined,
BondManager: undefined,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}
// Attach all L1 contracts.
const l1Contracts: OEL1Contracts = {} as any
for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
l1Contracts[contractName] = getOEContract(contractName as any, l1ChainId, {
address: opts.overrides?.l1?.[contractName] || contractAddress,
signerOrProvider: opts.l1SignerOrProvider,
})
}
// Attach all L2 contracts.
const l2Contracts: OEL2Contracts = {} as any
for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
l2Contracts[contractName] = getOEContract(contractName as any, l1ChainId, {
address: opts.overrides?.l2?.[contractName] || contractAddress,
signerOrProvider: opts.l2SignerOrProvider,
})
}
return {
l1: l1Contracts,
l2: l2Contracts,
}
}
export * from './coercion'
export * from './contracts'
export * from './message-encoding'
export * from './type-utils'
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { 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)]
)
}
/**
* Utility type for deep partials.
*/
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>
}
......@@ -3,16 +3,52 @@ import './setup'
describe('CrossChainProvider', () => {
describe('construction', () => {
describe('basic construction (given L1 and L2 providers)', () => {
it('should have an l1Provider', () => {})
describe('when given an ethers provider for the L1 provider', () => {
it('should use the provider as the L1 provider', () => {})
})
describe('when given an ethers provider for the L2 provider', () => {
it('should use the provider as the L2 provider', () => {})
})
describe('when given a string as the L1 provider', () => {
it('should create a JSON-RPC provider for the L1 provider', () => {})
})
describe('when given a string as the L2 provider', () => {
it('should create a JSON-RPC provider for the L2 provider', () => {})
})
describe('when no custom contract addresses are provided', () => {
describe('when given a known chain ID', () => {
it('should use the contract addresses for the known chain ID', () => {})
})
it('should have an l2Provider', () => {})
describe('when given an unknown chain ID', () => {
it('should throw an error', () => {})
})
})
it('should have an l1ChainId', () => {})
describe('when custom contract addresses are provided', () => {
describe('when given a known chain ID', () => {
it('should use known addresses except where custom addresses are given', () => {})
})
it('should have an l2ChainId', () => {})
describe('when given an unknown chain ID', () => {
describe('when all L1 addresses are provided', () => {
describe('when not all L2 addresses are provided', () => {
it('should use the custom L1 addresses and the default L2 addresses', () => {})
})
it('should have all contract connections', () => {})
describe('when all L2 addresses are provided', () => {
it('should use the custom addresses everywhere', () => {})
})
})
describe('when not all L1 addresses are provided', () => {
it('should throw an error', () => {})
})
})
})
})
......@@ -152,7 +188,7 @@ describe('CrossChainProvider', () => {
})
describe('when the message has not been relayed', () => {
it('should return a status of READY_FOR_RELAY')
it('should return a status of READY_FOR_RELAY', () => {})
})
})
})
......
import { expect } from './setup'
import { expect } from '../setup'
import { Provider } from '@ethersproject/abstract-provider'
import { Contract, Signer } from 'ethers'
import { Contract } 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)
})
})
import { toProvider, toTransactionHash } from '../../src'
describe('type coercion utils', () => {
describe('toProvider', () => {
it('should convert a string to a JsonRpcProvider', () => {
const provider = toProvider('http://localhost:8545')
......
This diff is collapsed.
import { expect } from '../setup'
import { Contract, Signer } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import {
CrossChainMessage,
MessageDirection,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../../src'
describe('message encoding 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)
})
})
})
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