Commit 2e28c7de authored by smartcontracts's avatar smartcontracts Committed by GitHub

Merge pull request #2136 from ethereum-optimism/develop

merge develop into master
parents 34649132 b971743b
---
'@eth-optimism/integration-tests': patch
---
Increase withdrawal test timeout
---
'@eth-optimism/data-transport-layer': patch
---
Updates DTL to correctly parse L1 to L2 tx timestamps after the first bss hardfork
---
'@eth-optimism/integration-tests': patch
---
Add an integration test showing the infeasability of withdrawing a fake token in exchange for a legitimate token.
---
'@eth-optimism/integration-tests': patch
---
Updates integration tests to include a test for syncing a Verifier from L1
---
'@eth-optimism/sdk': minor
---
Beta release of the Optimism SDK
---
'@eth-optimism/l2geth': patch
---
Fixes incorrect timestamp handling for L1 syncing verifiers
---
'@eth-optimism/batch-submitter-service': patch
---
fix BSS log-level flag parsing
---
'@eth-optimism/batch-submitter-service': patch
---
Adds a fix for the BSS to account for the new timestamp logic in L2Geth
---
'@eth-optimism/integration-tests': patch
---
Remove nightly itests - not needed anymore
---
'@eth-optimism/batch-submitter': patch
---
Updates batch submitter to also include separate timestamps for deposit transactions"
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/batch-submitter': patch
'@eth-optimism/contracts': patch
'@eth-optimism/core-utils': patch
'@eth-optimism/data-transport-layer': patch
'@eth-optimism/message-relayer': patch
'@eth-optimism/regenesis-surgery': patch
'@eth-optimism/replica-healthcheck': patch
'@eth-optimism/sdk': patch
---
Updates various ethers dependencies to their latest versions
---
'@eth-optimism/integration-tests': patch
---
Add verifier integration tests
---
'@eth-optimism/integration-tests': patch
---
Updates integration tests to start using SDK
---
'@eth-optimism/l2geth': patch
---
Bring back RPC methods that were previously blocked
---
'@eth-optimism/batch-submitter-service': patch
---
Restructure to use bss-core package
---
'@eth-optimism/sdk': patch
---
Have SDK include ethers as a peer dependency
.github
node_modules
.env
**/.env
test
**/*_test.go
......
This diff is collapsed.
......@@ -20,7 +20,7 @@ var (
func main() {
// Set up logger with a default INFO level in case we fail to parse flags.
// Otherwise the final crtiical log won't show what the parsing error was.
// Otherwise the final critical log won't show what the parsing error was.
log.Root().SetHandler(
log.LvlFilterHandler(
log.LvlInfo,
......
......@@ -190,6 +190,7 @@ func NewConfig(ctx *cli.Context) (Config, error) {
SafeMinimumEtherBalance: ctx.GlobalUint64(flags.SafeMinimumEtherBalanceFlag.Name),
ClearPendingTxs: ctx.GlobalBool(flags.ClearPendingTxsFlag.Name),
/* Optional Flags */
LogLevel: ctx.GlobalString(flags.LogLevelFlag.Name),
SentryEnable: ctx.GlobalBool(flags.SentryEnableFlag.Name),
SentryDsn: ctx.GlobalString(flags.SentryDsnFlag.Name),
SentryTraceRate: ctx.GlobalDuration(flags.SentryTraceRateFlag.Name),
......@@ -217,10 +218,6 @@ func NewConfig(ctx *cli.Context) (Config, error) {
// ensure that it is well-formed.
func ValidateConfig(cfg *Config) error {
// Sanity check log level.
if cfg.LogLevel == "" {
cfg.LogLevel = "debug"
}
_, err := log.LvlFromString(cfg.LogLevel)
if err != nil {
return err
......
......@@ -9,9 +9,9 @@ import (
"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/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/bss-core/drivers"
"github.com/ethereum-optimism/optimism/go/bss-core/metrics"
"github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum/go-ethereum/accounts/abi"
......
......@@ -91,11 +91,10 @@ func GenSequencerBatchParams(
// Iterate over the batch elements, grouping the elements according to
// the following critera:
// - All sequencer txs in the same group must have the same timestamp
// and block number.
// - All txs in the same group must have the same timestamp.
// - All sequencer txs in the same group must have the same block number.
// - If sequencer txs exist in a group, they must come before all
// queued txs.
// - A group should never split consecutive queued txs.
//
// Assuming the block and timestamp criteria for sequencer txs are
// respected within each group, the following are examples of groupings:
......@@ -110,17 +109,18 @@ func GenSequencerBatchParams(
// To enforce the above groupings, the following condition is
// used to determine when to create a new batch:
// - On the first pass, or
// - Whenever a sequecer tx is observed, and:
// - The preceding tx has a different timestamp, or
// - Whenever a sequencer tx is observed, and:
// - The preceding tx was a queued tx, or
// - The preceding sequencer tx has a different
// block number/timestamp.
// Note that a sequencer tx is required to create a new group,
// so a queued tx may ONLY exist as the first element in a group
// if it is the very first element.
// - The preceding sequencer tx has a different block number.
// Note that a sequencer tx is usually required to create a new group,
// so a queued tx may ONLY exist as the first element in a group if it
// is the very first element or it has a different timestamp from the
// preceding tx.
needsNewGroupOnSequencerTx := !lastBlockIsSequencerTx ||
el.Timestamp != lastTimestamp ||
el.BlockNumber != lastBlockNumber
if len(groupedBlocks) == 0 ||
el.Timestamp != lastTimestamp ||
(el.IsSequencerTx() && needsNewGroupOnSequencerTx) {
groupedBlocks = append(groupedBlocks, groupedBlock{})
......
......@@ -8,9 +8,9 @@ import (
"strings"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/bss-core/drivers"
"github.com/ethereum-optimism/optimism/go/bss-core/metrics"
"github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
......
......@@ -4,6 +4,7 @@ go 1.16
require (
github.com/decred/dcrd/hdkeychain/v3 v3.0.0
github.com/ethereum-optimism/optimism/go/bss-core v0.0.0
github.com/ethereum-optimism/optimism/l2geth v1.0.0
github.com/ethereum/go-ethereum v1.10.12
github.com/getsentry/sentry-go v0.11.0
......@@ -14,3 +15,5 @@ require (
)
replace github.com/ethereum-optimism/optimism/l2geth => ../../l2geth
replace github.com/ethereum-optimism/optimism/go/bss-core => ../bss-core
......@@ -177,6 +177,7 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlK
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
......@@ -197,10 +198,12 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
......@@ -409,6 +412,7 @@ github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5Vgl
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
......@@ -417,10 +421,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
......@@ -626,6 +632,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
......@@ -799,6 +806,7 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
......@@ -809,6 +817,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
......
/batch-submitter
package bsscore
import (
"context"
)
// BatchSubmitter is a service that configures the necessary resources for
// running the TxBatchSubmitter and StateBatchSubmitter sub-services.
type BatchSubmitter struct {
ctx context.Context
services []*Service
cancel func()
}
// NewBatchSubmitter initializes the BatchSubmitter, gathering any resources
// that will be needed by the TxBatchSubmitter and StateBatchSubmitter
// sub-services.
func NewBatchSubmitter(
ctx context.Context,
cancel func(),
services []*Service,
) (*BatchSubmitter, error) {
return &BatchSubmitter{
ctx: ctx,
services: services,
cancel: cancel,
}, nil
}
// Start starts all provided services.
func (b *BatchSubmitter) Start() error {
for _, service := range b.services {
if err := service.Start(); err != nil {
return err
}
}
return nil
}
// Stop stops all provided services and blocks until shutdown.
func (b *BatchSubmitter) Stop() {
b.cancel()
for _, service := range b.services {
_ = service.Stop()
}
}
package batchsubmitter
package bsscore
import (
"crypto/ecdsa"
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/tyler-smith/go-bip39"
)
......@@ -106,3 +107,69 @@ func ParsePrivateKeyStr(privKeyStr string) (*ecdsa.PrivateKey, error) {
hex := strings.TrimPrefix(privKeyStr, "0x")
return crypto.HexToECDSA(hex)
}
// ParseWalletPrivKeyAndContractAddr returns the wallet private key to use for
// sending transactions as well as the contract address to send to for a
// particular sub-service.
func ParseWalletPrivKeyAndContractAddr(
name string,
mnemonic string,
hdPath string,
privKeyStr string,
contractAddrStr string,
) (*ecdsa.PrivateKey, common.Address, error) {
// Parse wallet private key from either privkey string or BIP39 mnemonic
// and BIP32 HD derivation path.
privKey, err := GetConfiguredPrivateKey(mnemonic, hdPath, privKeyStr)
if err != nil {
return nil, common.Address{}, err
}
// Parse the target contract address the wallet will send to.
contractAddress, err := ParseAddress(contractAddrStr)
if err != nil {
return nil, common.Address{}, err
}
// Log wallet address rather than private key...
walletAddress := crypto.PubkeyToAddress(privKey.PublicKey)
log.Info(name+" wallet params parsed successfully", "wallet_address",
walletAddress, "contract_address", contractAddress)
return privKey, contractAddress, nil
}
// parseWalletPrivKeyAndContractAddr returns the wallet private key to use for
// sending transactions as well as the contract address to send to for a
// particular sub-service.
func parseWalletPrivKeyAndContractAddr(
name string,
mnemonic string,
hdPath string,
privKeyStr string,
contractAddrStr string,
) (*ecdsa.PrivateKey, common.Address, error) {
// Parse wallet private key from either privkey string or BIP39 mnemonic
// and BIP32 HD derivation path.
privKey, err := GetConfiguredPrivateKey(mnemonic, hdPath, privKeyStr)
if err != nil {
return nil, common.Address{}, err
}
// Parse the target contract address the wallet will send to.
contractAddress, err := ParseAddress(contractAddrStr)
if err != nil {
return nil, common.Address{}, err
}
// Log wallet address rather than private key...
walletAddress := crypto.PubkeyToAddress(privKey.PublicKey)
log.Info(name+" wallet params parsed successfully", "wallet_address",
walletAddress, "contract_address", contractAddress)
return privKey, contractAddress, nil
}
package batchsubmitter_test
package bsscore_test
import (
"bytes"
......@@ -6,7 +6,7 @@ import (
"strings"
"testing"
batchsubmitter "github.com/ethereum-optimism/optimism/go/batch-submitter"
bsscore "github.com/ethereum-optimism/optimism/go/bss-core"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
......@@ -76,7 +76,7 @@ func TestParseAddress(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
addr, err := batchsubmitter.ParseAddress(test.addr)
addr, err := bsscore.ParseAddress(test.addr)
require.Equal(t, err, test.expErr)
if test.expErr != nil {
return
......@@ -141,7 +141,7 @@ func TestDerivePrivateKey(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
privKey, err := batchsubmitter.DerivePrivateKey(test.mnemonic, test.hdPath)
privKey, err := bsscore.DerivePrivateKey(test.mnemonic, test.hdPath)
require.Equal(t, err, test.expErr)
if test.expErr != nil {
return
......@@ -193,7 +193,7 @@ func TestParsePrivateKeyStr(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
privKey, err := batchsubmitter.ParsePrivateKeyStr(test.privKeyStr)
privKey, err := bsscore.ParsePrivateKeyStr(test.privKeyStr)
require.Equal(t, err, test.expErr)
if test.expErr != nil {
return
......@@ -246,20 +246,20 @@ func TestGetConfiguredPrivateKey(t *testing.T) {
mnemonic: validMnemonic,
hdPath: validHDPath,
privKeyStr: validPrivKeyStr,
expErr: batchsubmitter.ErrCannotGetPrivateKey,
expErr: bsscore.ErrCannotGetPrivateKey,
},
{
name: "neither menmonic+hdpath or privkey",
mnemonic: "",
hdPath: "",
privKeyStr: "",
expErr: batchsubmitter.ErrCannotGetPrivateKey,
expErr: bsscore.ErrCannotGetPrivateKey,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
privKey, err := batchsubmitter.GetConfiguredPrivateKey(
privKey, err := bsscore.GetConfiguredPrivateKey(
test.mnemonic, test.hdPath, test.privKeyStr,
)
require.Equal(t, err, test.expErr)
......
package dial
import "time"
const (
// defaultDialTimeout is default duration the service will wait on
// startup to make a connection to either the L1 or L2 backends.
defaultDialTimeout = 5 * time.Second
)
package dial
import (
"context"
"crypto/tls"
"net/http"
"strings"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
// L1EthClientWithTimeout attempts to dial the L1 provider using the
// provided URL. If the dial doesn't complete within defaultDialTimeout seconds,
// this method will return an error.
func L1EthClientWithTimeout(ctx context.Context, url string, disableHTTP2 bool) (
*ethclient.Client, error) {
ctxt, cancel := context.WithTimeout(ctx, defaultDialTimeout)
defer cancel()
if strings.HasPrefix(url, "http") {
httpClient := new(http.Client)
if disableHTTP2 {
log.Info("Disabled HTTP/2 support in L1 eth client")
httpClient.Transport = &http.Transport{
TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
}
}
rpcClient, err := rpc.DialHTTPWithClient(url, httpClient)
if err != nil {
return nil, err
}
return ethclient.NewClient(rpcClient), nil
}
return ethclient.DialContext(ctxt, url)
}
package dial
import (
"context"
"crypto/tls"
"net/http"
"strings"
"github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum-optimism/optimism/l2geth/rpc"
)
// L2EthClientWithTimeout attempts to dial the L2 provider using the
// provided URL. If the dial doesn't complete within defaultDialTimeout seconds,
// this method will return an error.
func L2EthClientWithTimeout(ctx context.Context, url string, disableHTTP2 bool) (
*ethclient.Client, error) {
ctxt, cancel := context.WithTimeout(ctx, defaultDialTimeout)
defer cancel()
if strings.HasPrefix(url, "http") {
httpClient := new(http.Client)
if disableHTTP2 {
log.Info("Disabled HTTP/2 support in L2 eth client")
httpClient.Transport = &http.Transport{
TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
}
}
rpcClient, err := rpc.DialHTTPWithClient(url, httpClient)
if err != nil {
return nil, err
}
return ethclient.NewClient(rpcClient), nil
}
return ethclient.DialContext(ctxt, url)
}
......@@ -7,7 +7,7 @@ import (
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
......
......@@ -9,8 +9,8 @@ import (
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/mock"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/bss-core/mock"
"github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
......
module github.com/ethereum-optimism/optimism/go/bss-core
go 1.16
require (
github.com/decred/dcrd/hdkeychain/v3 v3.0.0
github.com/ethereum-optimism/optimism/l2geth v1.0.0
github.com/ethereum/go-ethereum v1.10.12
github.com/getsentry/sentry-go v0.11.0
github.com/prometheus/client_golang v1.11.0
github.com/stretchr/testify v1.7.0
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
)
replace github.com/ethereum-optimism/optimism/l2geth => ../../l2geth
This diff is collapsed.
package metrics
import (
"fmt"
"net/http"
"strconv"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// RunServer spins up a prometheus metrics server at the provided hostname and
// port.
//
// NOTE: This method MUST be run as a goroutine.
func RunServer(hostname string, port uint64) {
metricsPortStr := strconv.FormatUint(port, 10)
metricsAddr := fmt.Sprintf("%s:%s", hostname, metricsPortStr)
http.Handle("/metrics", promhttp.Handler())
_ = http.ListenAndServe(metricsAddr, nil)
}
package batchsubmitter
package bsscore
import (
"errors"
"io"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/getsentry/sentry-go"
......@@ -36,3 +37,14 @@ func SentryStreamHandler(wr io.Writer, fmtr log.Format) log.Handler {
})
return log.LazyHandler(log.SyncHandler(h))
}
// TraceRateToFloat64 converts a time.Duration into a valid float64 for the
// Sentry client. The client only accepts values between 0.0 and 1.0, so this
// method clamps anything greater than 1 second to 1.0.
func TraceRateToFloat64(rate time.Duration) float64 {
rate64 := float64(rate) / float64(time.Second)
if rate64 > 1.0 {
rate64 = 1.0
}
return rate64
}
package batchsubmitter
package bsscore
import (
"bytes"
......@@ -7,8 +7,8 @@ import (
"sync"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/bss-core/metrics"
"github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
......
......@@ -8,7 +8,7 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract FakeL2StandardERC20 {
address public immutable l1Token;
constructor(address _l1Token) {
l1Token = _l1Token;
}
// Burn will be called by the L2 Bridge to burn the tokens we are bridging to L1
function burn(address, uint256) external {}
}
......@@ -31,9 +31,10 @@
"@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5",
"@eth-optimism/message-relayer": "0.2.15",
"@eth-optimism/sdk": "0.0.7",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0",
"@ethersproject/providers": "^5.5.3",
"@ethersproject/transactions": "^5.5.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^4.4.0",
......@@ -62,7 +63,7 @@
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.3.0",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"hardhat": "^2.3.0",
"hardhat-gas-reporter": "^1.0.4",
"lint-staged": "11.0.0",
......
/* Imports: External */
import { Contract, ContractFactory } from 'ethers'
import { ethers } from 'hardhat'
import { applyL1ToL2Alias, awaitCondition } from '@eth-optimism/core-utils'
import { MessageDirection, MessageStatus } from '@eth-optimism/sdk'
import {
applyL1ToL2Alias,
awaitCondition,
sleep,
} from '@eth-optimism/core-utils'
/* Imports: Internal */
import { expect } from './shared/setup'
import { Direction } from './shared/watcher-utils'
import { OptimismEnv } from './shared/env'
import {
DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2,
envConfig,
sleep,
withdrawalTest,
} from './shared/utils'
......@@ -56,23 +59,35 @@ describe('Basic L1<>L2 Communication', async () => {
const value = `0x${'77'.repeat(32)}`
// Send L2 -> L1 message.
const transaction = await env.l2Messenger.sendMessage(
L1SimpleStorage.address,
L1SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000,
const transaction = await env.messenger.sendMessage(
{
direction: MessageDirection.L2_TO_L1,
target: L1SimpleStorage.address,
message: L1SimpleStorage.interface.encodeFunctionData('setValue', [
value,
]),
},
{
overrides: {
gasLimit: DEFAULT_TEST_GAS_L2,
},
}
)
await transaction.wait()
await env.relayXDomainMessages(transaction)
await env.waitForXDomainTransaction(transaction, Direction.L2ToL1)
let status: MessageStatus
while (status !== MessageStatus.READY_FOR_RELAY) {
status = await env.messenger.getMessageStatus(transaction)
await sleep(1000)
}
await env.messenger.finalizeMessage(transaction)
await env.messenger.waitForMessageReceipt(transaction)
expect(await L1SimpleStorage.msgSender()).to.equal(
env.l1Messenger.address
env.messenger.contracts.l1.L1CrossDomainMessenger.address
)
expect(await L1SimpleStorage.xDomainSender()).to.equal(
env.l2Wallet.address
await env.messenger.l2Signer.getAddress()
)
expect(await L1SimpleStorage.value()).to.equal(value)
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(1)
......@@ -85,25 +100,36 @@ describe('Basic L1<>L2 Communication', async () => {
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
const transaction = await env.l1Messenger.sendMessage(
L2SimpleStorage.address,
L2SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000,
const transaction = await env.messenger.sendMessage(
{
direction: MessageDirection.L1_TO_L2,
target: L2SimpleStorage.address,
message: L2SimpleStorage.interface.encodeFunctionData('setValue', [
value,
]),
},
{
l2GasLimit: 5000000,
overrides: {
gasLimit: DEFAULT_TEST_GAS_L1,
},
}
)
await env.waitForXDomainTransaction(transaction, Direction.L1ToL2)
const receipt = await env.messenger.waitForMessageReceipt(transaction)
console.log(await env.messenger.l2Signer.getAddress())
expect(receipt.transactionReceipt.status).to.equal(1)
expect(await L2SimpleStorage.msgSender()).to.equal(
env.l2Messenger.address
env.messenger.contracts.l2.L2CrossDomainMessenger.address
)
expect(await L2SimpleStorage.txOrigin()).to.equal(
applyL1ToL2Alias(env.l1Messenger.address)
applyL1ToL2Alias(
env.messenger.contracts.l1.L1CrossDomainMessenger.address
)
)
expect(await L2SimpleStorage.xDomainSender()).to.equal(
env.l1Wallet.address
await env.messenger.l1Signer.getAddress()
)
expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
......@@ -117,9 +143,10 @@ describe('Basic L1<>L2 Communication', async () => {
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
const tx = await env.ctc
.connect(env.l1Wallet)
.enqueue(
const tx =
await env.messenger.contracts.l1.CanonicalTransactionChain.connect(
env.messenger.l1Signer
).enqueue(
L2SimpleStorage.address,
5000000,
L2SimpleStorage.interface.encodeFunctionData('setValueNotXDomain', [
......@@ -129,11 +156,12 @@ describe('Basic L1<>L2 Communication', async () => {
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
const receipt = await tx.wait()
const waitUntilBlock =
receipt.blockNumber + envConfig.DTL_ENQUEUE_CONFIRMATIONS
let currBlock = await env.l1Provider.getBlockNumber()
let currBlock = await env.messenger.l1Provider.getBlockNumber()
while (currBlock <= waitUntilBlock) {
const progress =
envConfig.DTL_ENQUEUE_CONFIRMATIONS - (waitUntilBlock - currBlock)
......@@ -141,66 +169,28 @@ describe('Basic L1<>L2 Communication', async () => {
`Waiting for ${progress}/${envConfig.DTL_ENQUEUE_CONFIRMATIONS} confirmations.`
)
await sleep(5000)
currBlock = await env.l1Provider.getBlockNumber()
currBlock = await env.messenger.l1Provider.getBlockNumber()
}
console.log('Enqueue should be confirmed.')
await awaitCondition(
async () => {
const sender = await L2SimpleStorage.msgSender()
return sender === env.l1Wallet.address
return sender === (await env.messenger.l1Signer.getAddress())
},
2000,
60
)
// No aliasing when an EOA goes directly to L2.
expect(await L2SimpleStorage.msgSender()).to.equal(env.l1Wallet.address)
expect(await L2SimpleStorage.txOrigin()).to.equal(env.l1Wallet.address)
expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
it('should have a receipt with a status of 1 for a successful message', async () => {
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
const transaction = await env.l1Messenger.sendMessage(
L2SimpleStorage.address,
L2SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
await transaction.wait()
const { remoteReceipt } = await env.waitForXDomainTransaction(
transaction,
Direction.L1ToL2
)
expect(remoteReceipt.status).to.equal(1)
})
// SKIP: until we decide what should be done in this case
it.skip('should have a receipt with a status of 0 for a failed message', async () => {
// Send L1 -> L2 message.
const transaction = await env.l1Messenger.sendMessage(
L2Reverter.address,
L2Reverter.interface.encodeFunctionData('doRevert', []),
5000000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
expect(await L2SimpleStorage.msgSender()).to.equal(
await env.messenger.l1Signer.getAddress()
)
const { remoteReceipt } = await env.waitForXDomainTransaction(
transaction,
Direction.L1ToL2
expect(await L2SimpleStorage.txOrigin()).to.equal(
await env.messenger.l1Signer.getAddress()
)
expect(remoteReceipt.status).to.equal(0)
expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
})
})
......@@ -127,4 +127,58 @@ describe('Bridged tokens', () => {
)
}
)
// This test demonstrates that an apparent withdrawal bug is in fact non-existent.
// Specifically, the L2 bridge does not check that the L2 token being burned corresponds
// with the L1 token which is specified for the withdrawal.
withdrawalTest(
'should not allow an arbitrary L2 token to be withdrawn in exchange for a legitimate L1 token',
async () => {
before(async () => {
// First deposit some of the L1 token to L2, so that there is something which could be stolen.
const depositTx = await env.l1Bridge
.connect(env.l1Wallet)
.depositERC20(
L1__ERC20.address,
L2__ERC20.address,
1000,
2000000,
'0x'
)
await env.waitForXDomainTransaction(depositTx, Direction.L1ToL2)
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
BigNumber.from(1000)
)
})
// Deploy a Fake L2 token, which:
// - returns the address of a legitimate L1 token from its l1Token() getter.
// - allows the L2 bridge to call its burn() function.
const fakeToken = await (
await ethers.getContractFactory('FakeL2StandardERC20', env.l2Wallet)
).deploy(L1__ERC20.address)
await fakeToken.deployed()
const balBefore = await L1__ERC20.balanceOf(otherWalletL1.address)
// Withdraw some of the Fake L2 token, hoping to receive the same amount of the legitimate
// token on L1.
const withdrawalTx = await env.l2Bridge
.connect(otherWalletL2)
.withdrawTo(
fakeToken.address,
otherWalletL1.address,
500,
1_000_000,
'0x'
)
await env.relayXDomainMessages(withdrawalTx)
await env.waitForXDomainTransaction(withdrawalTx, Direction.L2ToL1)
// Ensure that the L1 recipient address has not received any additional L1 token balance.
expect(await L1__ERC20.balanceOf(otherWalletL1.address)).to.deep.equal(
balBefore
)
}
)
})
import { Contract } from 'ethers'
import { ethers } from 'hardhat'
import { OptimismEnv } from '../shared/env'
import { expect } from '../shared/setup'
import { traceToGasByOpcode } from '../hardfork.spec'
import { envConfig } from '../shared/utils'
describe('Nightly', () => {
before(async function () {
if (!envConfig.RUN_NIGHTLY_TESTS) {
this.skip()
}
})
describe('Berlin Hardfork', () => {
let env: OptimismEnv
let SimpleStorage: Contract
let Precompiles: Contract
before(async () => {
env = await OptimismEnv.new()
SimpleStorage = await ethers.getContractAt(
'SimpleStorage',
'0xE08fFE40748367ddc29B5A154331C73B7FCC13bD',
env.l2Wallet
)
Precompiles = await ethers.getContractAt(
'Precompiles',
'0x32E8Fbfd0C0bd1117112b249e997C27b0EC7cba2',
env.l2Wallet
)
})
describe('EIP-2929', () => {
it('should update the gas schedule', async () => {
const tx = await SimpleStorage.setValueNotXDomain(
`0x${'77'.repeat(32)}`
)
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
['0x2bb346f53544c5711502fbcbd1d78dc4fb61ca5f9390b9d6d67f1a3a77de7c39']
)
const berlinSstoreCosts = traceToGasByOpcode(
berlinTrace.structLogs,
'SSTORE'
)
const preBerlinSstoreCosts = traceToGasByOpcode(
preBerlinTrace.structLogs,
'SSTORE'
)
expect(preBerlinSstoreCosts).to.eq(80000)
expect(berlinSstoreCosts).to.eq(5300)
})
})
describe('EIP-2565', () => {
it('should become cheaper', async () => {
const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
['0x7ba7d273449b0062448fe5e7426bb169a032ce189d0e3781eb21079e85c2d7d5']
)
expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas)
})
})
describe('Berlin Additional (L1 London)', () => {
describe('EIP-3529', () => {
it('should remove the refund for selfdestruct', async () => {
const Factory__SelfDestruction = await ethers.getContractFactory(
'SelfDestruction',
env.l2Wallet
)
const SelfDestruction = await Factory__SelfDestruction.deploy()
const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[
'0x948667349f00e996d9267e5c30d72fe7202a0ecdb88bab191e9a022bba6e4cb3',
]
)
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
})
})
})
})
})
......@@ -11,7 +11,6 @@ import {
DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2,
envConfig,
PROXY_SEQUENCER_ENTRYPOINT_ADDRESS,
withdrawalTest,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
......@@ -31,9 +30,6 @@ describe('Native ETH Integration Tests', async () => {
const l1BobBalance = await l1Bob.getBalance()
const l2BobBalance = await l2Bob.getBalance()
const sequencerBalance = await _env.ovmEth.balanceOf(
PROXY_SEQUENCER_ENTRYPOINT_ADDRESS
)
const l1BridgeBalance = await _env.l1Wallet.provider.getBalance(
_env.l1Bridge.address
)
......@@ -44,7 +40,6 @@ describe('Native ETH Integration Tests', async () => {
l1BobBalance,
l2BobBalance,
l1BridgeBalance,
sequencerBalance,
}
}
......
......@@ -4,6 +4,7 @@ import { TransactionResponse } from '@ethersproject/providers'
import { getContractFactory, predeploys } from '@eth-optimism/contracts'
import { Watcher } from '@eth-optimism/core-utils'
import { getMessagesAndProofsForL2Transaction } from '@eth-optimism/message-relayer'
import { CrossChainMessenger } from '@eth-optimism/sdk'
/* Imports: Internal */
import {
......@@ -11,6 +12,7 @@ import {
l1Provider,
l2Provider,
replicaProvider,
verifierProvider,
l1Wallet,
l2Wallet,
gasPriceOracleWallet,
......@@ -54,9 +56,11 @@ export class OptimismEnv {
l2Wallet: Wallet
// The providers
messenger: CrossChainMessenger
l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider
replicaProvider: providers.JsonRpcProvider
verifierProvider: providers.JsonRpcProvider
constructor(args: any) {
this.addressManager = args.addressManager
......@@ -71,9 +75,11 @@ export class OptimismEnv {
this.watcher = args.watcher
this.l1Wallet = args.l1Wallet
this.l2Wallet = args.l2Wallet
this.messenger = args.messenger
this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider
this.replicaProvider = args.replicaProvider
this.verifierProvider = args.verifierProvider
this.ctc = args.ctc
this.scc = args.scc
}
......@@ -123,6 +129,13 @@ export class OptimismEnv {
.connect(l2Wallet)
.attach(predeploys.OVM_L1BlockNumber)
const network = await l1Provider.getNetwork()
const messenger = new CrossChainMessenger({
l1SignerOrProvider: l1Wallet,
l2SignerOrProvider: l2Wallet,
l1ChainId: network.chainId,
})
return new OptimismEnv({
addressManager,
l1Bridge,
......@@ -138,8 +151,10 @@ export class OptimismEnv {
watcher,
l1Wallet,
l2Wallet,
messenger,
l1Provider,
l2Provider,
verifierProvider,
replicaProvider,
})
}
......
......@@ -62,6 +62,8 @@ const procEnv = cleanEnv(process.env, {
REPLICA_URL: str({ default: 'http://localhost:8549' }),
REPLICA_POLLING_INTERVAL: num({ default: 10 }),
VERIFIER_URL: str({ default: 'http://localhost:8547' }),
PRIVATE_KEY: str({
default:
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
......@@ -93,8 +95,8 @@ const procEnv = cleanEnv(process.env, {
RUN_STRESS_TESTS: bool({
default: true,
}),
RUN_NIGHTLY_TESTS: bool({
default: false,
RUN_VERIFIER_TESTS: bool({
default: true,
}),
MOCHA_TIMEOUT: num({
......@@ -121,6 +123,11 @@ export const replicaProvider = injectL2Context(
)
replicaProvider.pollingInterval = procEnv.REPLICA_POLLING_INTERVAL
export const verifierProvider = injectL2Context(
new providers.JsonRpcProvider(procEnv.VERIFIER_URL)
)
verifierProvider.pollingInterval = procEnv.L2_POLLING_INTERVAL
// The sequencer private key which is funded on L1
export const l1Wallet = new Wallet(procEnv.PRIVATE_KEY, l1Provider)
......@@ -135,8 +142,6 @@ export const gasPriceOracleWallet = new Wallet(
)
// Predeploys
export const PROXY_SEQUENCER_ENTRYPOINT_ADDRESS =
'0x4200000000000000000000000000000000000004'
export const OVM_ETH_ADDRESS = predeploys.OVM_ETH
export const L2_CHAINID = procEnv.L2_CHAINID
......@@ -210,7 +215,7 @@ export const conditionalTest = (
}
await fn()
}).timeout(timeout || envConfig.MOCHA_TIMEOUT)
}).timeout(timeout || envConfig.MOCHA_TIMEOUT * 2)
}
export const withdrawalTest = (name, fn, timeout?: number) =>
......
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import {
defaultTransactionFactory,
gasPriceForL2,
sleep,
envConfig,
} from './shared/utils'
describe('Verifier Tests', () => {
let env: OptimismEnv
before(async function () {
if (!envConfig.RUN_VERIFIER_TESTS) {
this.skip()
return
}
env = await OptimismEnv.new()
})
describe('Matching blocks', () => {
it('should sync a transaction', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2()
const result = await env.l2Wallet.sendTransaction(tx)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.verifierProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const verifierBlock = (await env.verifierProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(verifierBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(verifierBlock.hash)
})
it('sync an unprotected tx (eip155)', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.l2Provider.sendTransaction(signed)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.verifierProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const verifierBlock = (await env.verifierProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(verifierBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(verifierBlock.hash)
})
it('should forward tx to sequencer', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(),
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.verifierProvider.sendTransaction(signed)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.verifierProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const verifierBlock = (await env.verifierProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(verifierBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(verifierBlock.hash)
})
})
})
......@@ -16,7 +16,7 @@ configuration will determine the mode of operation. The configuration flags
can be configured using either environment variables or passed at runtime as
flags.
A prebuilt Docker image is available at `ethereumoptimism/go-ethereum`.
A prebuilt Docker image is available at `ethereumoptimism/l2geth`.
To compile the code, run:
```
......@@ -25,46 +25,39 @@ $ make geth
### Running a Sequencer
Running a sequencer requires the [Data Transport Layer](https://github.com/ethereum-optimism/data-transport-layer)
to be synced. The data transport layer is responsible for indexing transactions
from Layer One concurrently. The sequencer pulls in transactions from the data
transport layer and executes them. The URL of the data transport layer should be
Running a sequencer that ingests L1 to L2 transactions requires running the
[Data Transport Layer](https://github.com/ethereum-optimism/data-transport-layer).
The data transport layer is responsible for indexing transactions
from layer one Ethereum. It is possible to run a local development sequencer
without the data transport layer by turning off the sync service. To turn on
the sync service, use the config flag `--eth1.syncservice` or
`ETH1_SYNC_SERVICE_ENABLE`. The URL of the data transport layer should be
used for the sequencer config option `--rollup.clienthttp`.
See the script `scripts/start.sh`. It sets many of the config options
and accepts CLI flags. For usage, run the command:
The `scripts` directory contains some scripts that make it easy to run a
local sequencer for development purposes.
First, the genesis block must be initialized. This is because there are
predeployed contracts in the L2 state. The scripts to generate the genesis
block can be found in the `contracts` package. Be sure to run those first.
```bash
$ ./scripts/start.sh -h
$ ./scripts/init.sh
```
This may be suitable for simple usecases, users that need more flexibility
with their configuration can run the command:
This script can be ran with the `DEVELOPMENT` env var set which will add
a prefunded account to the genesis state that can be used for development.
The `start.sh` script is used to start `geth`. It hardcodes a bunch of
common config values for when running `geth`.
```bash
$ USING_OVM=true ./build/bin/geth \
--rollup.clienthttp $ROLLUP_CLIENT_HTTP \
--rollup.pollinterval 3s \
--eth1.ctcdeploymentheight $CTC_DEPLOY_HEIGHT \
--eth1.syncservice \
--rpc \
--dev \
--rpcaddr "0.0.0.0" \
--rpccorsdomain '*' \
--wsaddr "0.0.0.0" \
--wsport 8546 \
--wsorigins '*' \
--rpcapi 'eth,net,rollup,web3' \
--gasprice '0' \
--targetgaslimit '8000000' \
--nousb \
--gcmode=archive \
--ipcdisable
$ ./scripts/start.sh
```
To persist the database, pass the `--datadir` with a path to the directory for
the database to be persisted in. Without this flag, an in memory database will
be used. To tune the log level, use the `--verbosity` flag with an integer.
This script can be modified to work with `dlv` by prefixing the `$cmd`
with `dlv exec` and being sure to prefix the `geth` arguments with `--`
so they are interpreted as arguments to `geth` instead of `dlv`.
### Running a Verifier
......
......@@ -51,7 +51,6 @@ import (
)
var (
errOVMUnsupported = errors.New("OVM: Unsupported RPC Method")
errNoSequencerURL = errors.New("sequencer transaction forwarding not configured")
)
......@@ -386,6 +385,19 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs
log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
return common.Hash{}, err
}
if s.b.IsVerifier() {
client, err := dialSequencerClientWithTimeout(ctx, s.b.SequencerClientHttp())
if err != nil {
return common.Hash{}, err
}
err = client.SendTransaction(context.Background(), signed)
if err != nil {
return common.Hash{}, err
}
return signed.Hash(), nil
}
return SubmitTransaction(ctx, s.b, signed)
}
......@@ -1619,9 +1631,6 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
if rcfg.UsingOVM {
return common.Hash{}, errOVMUnsupported
}
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.From}
......@@ -1654,9 +1663,6 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
// FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction,
// and returns it to the caller for further processing (signing + broadcast)
func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
if rcfg.UsingOVM {
return nil, errOVMUnsupported
}
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err
......@@ -1714,9 +1720,6 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
if rcfg.UsingOVM {
return nil, errOVMUnsupported
}
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
......@@ -1742,9 +1745,6 @@ type SignTransactionResult struct {
// The node needs to have the private key of the account corresponding with
// the given from address and it needs to be unlocked.
func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
if rcfg.UsingOVM {
return nil, errOVMUnsupported
}
if args.Gas == nil {
return nil, fmt.Errorf("gas not specified")
}
......
......@@ -931,18 +931,7 @@ func (w *worker) commitNewTx(tx *types.Transaction) error {
// Preserve liveliness as best as possible. Must panic on L1 to L2
// transactions as the timestamp cannot be malleated
if parent.Time() > tx.L1Timestamp() {
log.Error("Monotonicity violation", "index", num)
if tx.QueueOrigin() == types.QueueOriginSequencer {
tx.SetL1Timestamp(parent.Time())
prev := parent.Transactions()
if len(prev) == 1 {
tx.SetL1BlockNumber(prev[0].L1BlockNumber().Uint64())
} else {
log.Error("Cannot recover L1 Blocknumber")
}
} else {
log.Error("Cannot recover from monotonicity violation")
}
log.Error("Monotonicity violation", "index", num, "parent", parent.Time(), "tx", tx.L1Timestamp())
}
// Fill in the index field in the tx meta if it is `nil`.
......
......@@ -825,13 +825,14 @@ func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error {
if now.Sub(current) > s.timestampRefreshThreshold {
current = now
}
log.Info("Updating latest timestamp", "timestamp", current, "unix", current.Unix())
tx.SetL1Timestamp(uint64(current.Unix()))
} else if tx.L1Timestamp() == 0 && s.verifier {
// This should never happen
log.Error("No tx timestamp found when running as verifier", "hash", tx.Hash().Hex())
} else if tx.L1Timestamp() < s.GetLatestL1Timestamp() {
} else if tx.L1Timestamp() < ts {
// This should never happen, but sometimes does
log.Error("Timestamp monotonicity violation", "hash", tx.Hash().Hex())
log.Error("Timestamp monotonicity violation", "hash", tx.Hash().Hex(), "latest", ts, "tx", tx.L1Timestamp())
}
l1BlockNumber := tx.L1BlockNumber()
......
......@@ -136,8 +136,9 @@ services:
- l1_chain
- deployer
- dtl
- l2geth
deploy:
replicas: 0
replicas: 1
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.geth
......@@ -146,6 +147,7 @@ services:
- ./envs/geth.env
environment:
ETH1_HTTP: http://l1_chain:8545
SEQUENCER_CLIENT_HTTP: http://l2geth:8545
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
ROLLUP_CLIENT_HTTP: http://dtl:7878
ROLLUP_BACKEND: 'l1'
......
FROM golang:1.17.6-alpine3.15 as builder
FROM golang:1.17.3-alpine3.13 as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash
COPY ./l2geth /l2geth
COPY ./go/bss-core /go/bss-core
COPY ./go/batch-submitter/go.mod ./go/batch-submitter/go.sum /go/batch-submitter/
WORKDIR /go/batch-submitter
RUN go mod graph | grep -v l2geth | awk '{if ($1 !~ "@") print $2}' | xargs -n 1 go get
RUN go mod graph | grep -v l2geth | grep -v bss-core | awk '{if ($1 !~ "@") print $2}' | xargs -n 1 go get
COPY ./go/batch-submitter/ ./
RUN make
FROM alpine:3.15
FROM alpine:3.13
RUN apk add --no-cache ca-certificates jq curl
COPY --from=builder /go/batch-submitter/batch-submitter /usr/local/bin/
......
......@@ -12,6 +12,9 @@ COPY --from=builder /optimism/*.json /optimism/yarn.lock ./
COPY --from=builder /optimism/node_modules ./node_modules
# copy deps (would have been nice if docker followed the symlinks required)
COPY --from=builder /optimism/packages/sdk/package.json ./packages/sdk/package.json
COPY --from=builder /optimism/packages/sdk/dist ./packages/sdk/dist
COPY --from=builder /optimism/packages/core-utils/package.json ./packages/core-utils/package.json
COPY --from=builder /optimism/packages/core-utils/dist ./packages/core-utils/dist
......
......@@ -13,6 +13,7 @@ RUN apt-get update -y && apt-get install -y git curl jq python3
# us to cache the installation steps
WORKDIR /opt/optimism
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
......
......@@ -9,6 +9,7 @@ DATA_TRANSPORT_LAYER__LOGS_PER_POLLING_INTERVAL=2000
DATA_TRANSPORT_LAYER__DANGEROUSLY_CATCH_ALL_ERRORS=true
DATA_TRANSPORT_LAYER__SERVER_HOSTNAME=0.0.0.0
DATA_TRANSPORT_LAYER__L1_START_HEIGHT=1
DATA_TRANSPORT_LAYER__BSS_HARDFORK_1_INDEX=0
DATA_TRANSPORT_LAYER__ADDRESS_MANAGER=
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT=
......
......@@ -37,13 +37,13 @@
"@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5",
"@eth-optimism/ynatm": "^0.2.2",
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.5.3",
"@sentry/node": "^6.3.1",
"bcfg": "^0.1.6",
"bluebird": "^3.7.2",
"dotenv": "^10.0.0",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"old-contracts": "npm:@eth-optimism/contracts@^0.0.2-alpha.7",
"prom-client": "^13.1.0"
},
......
ignores: [
"@codechecks/client",
"@ethersproject/transactions",
"@openzeppelin/contracts",
"@openzeppelin/contracts-upgradeable",
"@typechain/ethers-v5",
......
......@@ -59,15 +59,14 @@
},
"dependencies": {
"@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/abstract-signer": "^5.4.1",
"@ethersproject/hardware-wallets": "^5.4.0"
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abstract-signer": "^5.5.0",
"@ethersproject/hardware-wallets": "^5.5.0"
},
"devDependencies": {
"@codechecks/client": "^0.1.11",
"@defi-wonderland/smock": "^2.0.2",
"@eth-optimism/smock": "1.1.10",
"@ethersproject/transactions": "^5.4.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.6",
"@nomiclabs/hardhat-waffle": "^2.0.1",
......@@ -96,7 +95,7 @@
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.3.0",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"glob": "^7.1.6",
"hardhat": "^2.3.0",
"hardhat-deploy": "^0.9.3",
......@@ -123,6 +122,6 @@
"yargs": "^16.2.0"
},
"peerDependencies": {
"ethers": "^5.4.5"
"ethers": "^5"
}
}
......@@ -32,12 +32,12 @@
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/bytes": "^5.5.0",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/web": "^5.5.0",
"@ethersproject/providers": "^5.5.3",
"@ethersproject/web": "^5.5.1",
"chai": "^4.3.4",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"lodash": "^4.17.21"
},
"devDependencies": {
......
......@@ -39,8 +39,8 @@
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0",
"@ethersproject/providers": "^5.5.3",
"@ethersproject/transactions": "^5.5.0",
"@sentry/node": "^6.3.1",
"@sentry/tracing": "^6.3.1",
"@types/express": "^4.17.12",
......@@ -50,7 +50,7 @@
"browser-or-node": "^1.3.0",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"express": "^4.17.1",
"express-prom-bundle": "^6.3.6",
"level": "^6.0.1",
......@@ -58,7 +58,6 @@
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@ethersproject/abstract-provider": "^5.4.1",
"@types/browser-or-node": "^1.3.0",
"@types/chai": "^4.2.18",
"@types/chai-as-promised": "^7.1.4",
......
......@@ -31,11 +31,17 @@ interface Indexed {
index: number
}
interface ExtraTransportDBOptions {
bssHardfork1Index?: number
}
export class TransportDB {
public db: SimpleDB
public opts: ExtraTransportDBOptions
constructor(leveldb: LevelUp) {
constructor(leveldb: LevelUp, opts?: ExtraTransportDBOptions) {
this.db = new SimpleDB(leveldb)
this.opts = opts || {}
}
public async putEnqueueEntries(entries: EnqueueEntry[]): Promise<void> {
......@@ -254,26 +260,7 @@ export class TransportDB {
return null
}
if (transaction.queueOrigin === 'l1') {
const enqueue = await this.getEnqueueByIndex(transaction.queueIndex)
if (enqueue === null) {
return null
}
return {
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp: enqueue.timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
}
} else {
return transaction
}
return this._makeFullTransaction(transaction)
}
public async getLatestFullTransaction(): Promise<TransactionEntry> {
......@@ -293,29 +280,44 @@ export class TransportDB {
const fullTransactions = []
for (const transaction of transactions) {
if (transaction.queueOrigin === 'l1') {
fullTransactions.push(await this._makeFullTransaction(transaction))
}
return fullTransactions
}
private async _makeFullTransaction(
transaction: TransactionEntry
): Promise<TransactionEntry> {
// We only need to do extra work for L1 to L2 transactions.
if (transaction.queueOrigin !== 'l1') {
return transaction
}
const enqueue = await this.getEnqueueByIndex(transaction.queueIndex)
if (enqueue === null) {
return null
}
fullTransactions.push({
let timestamp = enqueue.timestamp
if (
typeof this.opts.bssHardfork1Index === 'number' &&
transaction.index >= this.opts.bssHardfork1Index
) {
timestamp = transaction.timestamp
}
return {
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp: enqueue.timestamp,
timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
})
} else {
fullTransactions.push(transaction)
}
}
return fullTransactions
}
private async _getLatestEntryIndex(key: string): Promise<number> {
......
......@@ -18,7 +18,7 @@ import {
TransactionEntry,
EventHandlerSet,
} from '../../../types'
import { SEQUENCER_GAS_LIMIT, parseSignatureVParam } from '../../../utils'
import { parseSignatureVParam } from '../../../utils'
export const handleEventsSequencerBatchAppended: EventHandlerSet<
SequencerBatchAppendedEvent,
......@@ -65,7 +65,6 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
submitter: l1Transaction.from,
l1TransactionHash: l1Transaction.hash,
l1TransactionData: l1Transaction.data,
gasLimit: `${SEQUENCER_GAS_LIMIT}`,
prevTotalElements: batchSubmissionEvent.args._prevTotalElements,
batchIndex: batchSubmissionEvent.args._batchIndex,
......@@ -143,7 +142,7 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
.toNumber(),
batchIndex: extraData.batchIndex.toNumber(),
blockNumber: BigNumber.from(0).toNumber(),
timestamp: BigNumber.from(0).toNumber(),
timestamp: context.timestamp,
gasLimit: BigNumber.from(0).toString(),
target: constants.AddressZero,
origin: constants.AddressZero,
......
......@@ -104,7 +104,9 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> {
} = {} as any
protected async _init(): Promise<void> {
this.state.db = new TransportDB(this.options.db)
this.state.db = new TransportDB(this.options.db, {
bssHardfork1Index: this.options.bssHardfork1Index,
})
this.l1IngestionMetrics = registerMetrics(this.metrics)
......
......@@ -84,7 +84,9 @@ export class L2IngestionService extends BaseService<L2IngestionServiceOptions> {
this.l2IngestionMetrics = registerMetrics(this.metrics)
this.state.db = new TransportDB(this.options.db)
this.state.db = new TransportDB(this.options.db, {
bssHardfork1Index: this.options.bssHardfork1Index,
})
this.state.l2RpcProvider =
typeof this.options.l2RpcProvider === 'string'
......
......@@ -36,6 +36,7 @@ export interface L1DataTransportServiceOptions {
defaultBackend: string
l1GasPriceBackend: string
l1StartHeight?: number
bssHardfork1Index?: number
}
const optionSettings = {
......
......@@ -51,6 +51,7 @@ type ethNetwork = 'mainnet' | 'kovan' | 'goerli'
useSentry: config.bool('use-sentry', false),
sentryDsn: config.str('sentry-dsn'),
sentryTraceRate: config.ufloat('sentry-trace-rate', 0.05),
bssHardfork1Index: config.uint('bss-hardfork-1-index', null),
})
const stop = async (signal) => {
......
......@@ -87,7 +87,10 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> {
await this.options.db.open()
}
this.state.db = new TransportDB(this.options.db)
this.state.db = new TransportDB(this.options.db, {
bssHardfork1Index: this.options.bssHardfork1Index,
})
this.state.l1RpcProvider =
typeof this.options.l1RpcProvider === 'string'
? new JsonRpcProvider(this.options.l1RpcProvider)
......
......@@ -42,7 +42,6 @@ export interface SequencerBatchAppendedExtraData {
submitter: string
l1TransactionData: string
l1TransactionHash: string
gasLimit: string
// Stuff from TransactionBatchAppended.
prevTotalElements: BigNumber
......
export const SEQUENCER_GAS_LIMIT = 11_000_000 // TODO: Remove and use value from event.
export const SEQUENCER_ENTRYPOINT_ADDRESS =
'0x4200000000000000000000000000000000000005'
export * from './common'
export * from './constants'
export * from './contracts'
export * from './validation'
export * from './eth-tx'
/* Imports: External */
import { BigNumber } from 'ethers'
import { Block } from '@ethersproject/abstract-provider'
/* Imports: Internal */
import { expect } from '../../../../setup'
......@@ -18,7 +17,7 @@ describe('Event Handlers: CanonicalTransactionChain.StateBatchAppended', () => {
data: l1StateBatchData,
}
// Source: https://etherscan.io/block/12106615
const eventBlock: Block = {
const eventBlock = {
timestamp: 1616680530,
number: 12106615,
hash: '0x9c40310e19e943ad38e170329465c4489f6aba5895e9cacdac236be181aea31f',
......
......@@ -40,7 +40,7 @@
"@sentry/node": "^6.3.1",
"bcfg": "^0.1.6",
"dotenv": "^10.0.0",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"merkletreejs": "^0.2.18",
"rlp": "^2.2.6"
},
......
......@@ -33,11 +33,11 @@
"devDependencies": {
"@discoveryjs/json-ext": "^0.5.3",
"@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abi": "^5.5.0",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/properties": "^5.5.0",
"@ethersproject/providers": "^5.5.0",
"@ethersproject/providers": "^5.5.3",
"@types/node": "^15.12.2",
"@types/node-fetch": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^4.26.0",
......@@ -59,7 +59,7 @@
"eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.4.0",
"ethereumjs-util": "^7.1.3",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"lint-staged": "11.0.0",
"mocha": "^9.1.2",
"node-fetch": "2.6.7",
......
......@@ -35,7 +35,7 @@
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/core-utils": "0.7.5",
"dotenv": "^10.0.0",
"ethers": "^5.4.5",
"ethers": "^5.5.4",
"express": "^4.17.1",
"express-prom-bundle": "^6.3.6",
"lint-staged": "11.0.0",
......
......@@ -31,6 +31,8 @@
"url": "https://github.com/ethereum-optimism/optimism-monorepo.git"
},
"devDependencies": {
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abstract-signer": "^5.5.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/chai": "^4.2.18",
......@@ -50,6 +52,7 @@
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.4.0",
"ethers": "^5.5.4",
"hardhat": "^2.3.0",
"lint-staged": "11.0.0",
"mocha": "^8.4.0",
......@@ -61,8 +64,10 @@
"dependencies": {
"@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abstract-signer": "^5.5.0",
"ethers": "^5.5.2"
"merkletreejs": "^0.2.27",
"rlp": "^2.2.7"
},
"peerDependencies": {
"ethers": "^5"
}
}
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Contract } from 'ethers'
import { hexStringEquals } from '@eth-optimism/core-utils'
import { AddressLike } from '../interfaces'
import { toAddress } from '../utils'
import { StandardBridgeAdapter } from './standard-bridge'
/**
* Bridge adapter for DAI.
*/
export class DAIBridgeAdapter extends StandardBridgeAdapter {
public async supportsTokenPair(
l1Token: AddressLike,
l2Token: AddressLike
): Promise<boolean> {
// Just need access to this ABI for this one function.
const l1Bridge = new Contract(
this.l1Bridge.address,
[
{
inputs: [],
name: 'l1Token',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'l2Token',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
],
this.messenger.l1Provider
)
const allowedL1Token = await l1Bridge.l1Token()
if (!hexStringEquals(allowedL1Token, toAddress(l1Token))) {
return false
}
const allowedL2Token = await l1Bridge.l2Token()
if (!hexStringEquals(allowedL2Token, toAddress(l2Token))) {
return false
}
return true
}
}
/* eslint-disable @typescript-eslint/no-unused-vars */
import { ethers, Overrides } from 'ethers'
import { TransactionRequest, BlockTag } from '@ethersproject/abstract-provider'
import { predeploys } from '@eth-optimism/contracts'
import { hexStringEquals } from '@eth-optimism/core-utils'
import {
NumberLike,
AddressLike,
TokenBridgeMessage,
MessageDirection,
} from '../interfaces'
import { toAddress, omit } from '../utils'
import { StandardBridgeAdapter } from './standard-bridge'
/**
* Bridge adapter for the ETH bridge.
*/
export class ETHBridgeAdapter extends StandardBridgeAdapter {
public async getDepositsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]> {
const events = await this.l1Bridge.queryFilter(
this.l1Bridge.filters.ETHDepositInitiated(address),
opts?.fromBlock,
opts?.toBlock
)
return events.map((event) => {
return {
direction: MessageDirection.L1_TO_L2,
from: event.args._from,
to: event.args._to,
l1Token: ethers.constants.AddressZero,
l2Token: predeploys.OVM_ETH,
amount: event.args._amount,
data: event.args._data,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
}
})
}
public async getWithdrawalsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]> {
const events = await this.l2Bridge.queryFilter(
this.l2Bridge.filters.WithdrawalInitiated(undefined, undefined, address),
opts?.fromBlock,
opts?.toBlock
)
return events
.filter((event) => {
// Only find ETH withdrawals.
return (
hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) &&
hexStringEquals(event.args._l2Token, predeploys.OVM_ETH)
)
})
.map((event) => {
return {
direction: MessageDirection.L2_TO_L1,
from: event.args._from,
to: event.args._to,
l1Token: event.args._l1Token,
l2Token: event.args._l2Token,
amount: event.args._amount,
data: event.args._data,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
}
})
}
public async supportsTokenPair(
l1Token: AddressLike,
l2Token: AddressLike
): Promise<boolean> {
// Only support ETH deposits and withdrawals.
return (
hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) &&
hexStringEquals(toAddress(l2Token), predeploys.OVM_ETH)
)
}
populateTransaction = {
deposit: async (
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionRequest> => {
if (!(await this.supportsTokenPair(l1Token, l2Token))) {
throw new Error(`token pair not supported by bridge`)
}
return this.l1Bridge.populateTransaction.depositETH(
opts?.l2GasLimit || 200_000, // Default to 200k gas limit.
'0x', // No data.
{
...omit(opts?.overrides || {}, 'value'),
value: amount,
}
)
},
withdraw: async (
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionRequest> => {
if (!(await this.supportsTokenPair(l1Token, l2Token))) {
throw new Error(`token pair not supported by bridge`)
}
return this.l2Bridge.populateTransaction.withdraw(
toAddress(l2Token),
amount,
0, // L1 gas not required.
'0x', // No data.
opts?.overrides || {}
)
},
}
}
export * from './standard-bridge'
export * from './eth-bridge'
export * from './dai-bridge'
/* eslint-disable @typescript-eslint/no-unused-vars */
import { ethers, Contract, Overrides, Signer, BigNumber } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
BlockTag,
} from '@ethersproject/abstract-provider'
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { hexStringEquals } from '@eth-optimism/core-utils'
import {
IBridgeAdapter,
ICrossChainMessenger,
NumberLike,
AddressLike,
TokenBridgeMessage,
MessageDirection,
} from '../interfaces'
import { toAddress } from '../utils'
/**
* Bridge adapter for any token bridge that uses the standard token bridge interface.
*/
export class StandardBridgeAdapter implements IBridgeAdapter {
public messenger: ICrossChainMessenger
public l1Bridge: Contract
public l2Bridge: Contract
/**
* Creates a StandardBridgeAdapter instance.
*
* @param opts Options for the adapter.
* @param opts.messenger Provider used to make queries related to cross-chain interactions.
* @param opts.l1Bridge L1 bridge contract.
* @param opts.l2Bridge L2 bridge contract.
*/
constructor(opts: {
messenger: ICrossChainMessenger
l1Bridge: AddressLike
l2Bridge: AddressLike
}) {
this.messenger = opts.messenger
this.l1Bridge = new Contract(
toAddress(opts.l1Bridge),
getContractInterface('L1StandardBridge'),
this.messenger.l1Provider
)
this.l2Bridge = new Contract(
toAddress(opts.l2Bridge),
getContractInterface('IL2ERC20Bridge'),
this.messenger.l2Provider
)
}
public async getTokenBridgeMessagesByAddress(
address: AddressLike,
opts?: {
direction?: MessageDirection
}
): Promise<TokenBridgeMessage[]> {
let messages: TokenBridgeMessage[] = []
if (
opts?.direction === undefined ||
opts?.direction === MessageDirection.L1_TO_L2
) {
messages = messages.concat(await this.getDepositsByAddress(address))
}
if (
opts?.direction === undefined ||
opts?.direction === MessageDirection.L2_TO_L1
) {
messages = messages.concat(await this.getWithdrawalsByAddress(address))
}
return messages
}
public async getDepositsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]> {
const events = await this.l1Bridge.queryFilter(
this.l1Bridge.filters.ERC20DepositInitiated(
undefined,
undefined,
address
),
opts?.fromBlock,
opts?.toBlock
)
return events
.filter((event) => {
// Specifically filter out ETH. ETH deposits and withdrawals are handled by the ETH bridge
// adapter. Bridges that are not the ETH bridge should not be able to handle or even
// present ETH deposits or withdrawals.
return (
!hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args._l2Token, predeploys.OVM_ETH)
)
})
.map((event) => {
return {
direction: MessageDirection.L1_TO_L2,
from: event.args._from,
to: event.args._to,
l1Token: event.args._l1Token,
l2Token: event.args._l2Token,
amount: event.args._amount,
data: event.args._data,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
}
})
}
public async getWithdrawalsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]> {
const events = await this.l2Bridge.queryFilter(
this.l2Bridge.filters.WithdrawalInitiated(undefined, undefined, address),
opts?.fromBlock,
opts?.toBlock
)
return events
.filter((event) => {
// Specifically filter out ETH. ETH deposits and withdrawals are handled by the ETH bridge
// adapter. Bridges that are not the ETH bridge should not be able to handle or even
// present ETH deposits or withdrawals.
return (
!hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args._l2Token, predeploys.OVM_ETH)
)
})
.map((event) => {
return {
direction: MessageDirection.L2_TO_L1,
from: event.args._from,
to: event.args._to,
l1Token: event.args._l1Token,
l2Token: event.args._l2Token,
amount: event.args._amount,
data: event.args._data,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
}
})
}
public async supportsTokenPair(
l1Token: AddressLike,
l2Token: AddressLike
): Promise<boolean> {
try {
const contract = new Contract(
toAddress(l2Token),
getContractInterface('L2StandardERC20'),
this.messenger.l2Provider
)
// Don't support ETH deposits or withdrawals via this bridge.
if (
hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) ||
hexStringEquals(toAddress(l2Token), predeploys.OVM_ETH)
) {
return false
}
// Make sure the L1 token matches.
const remoteL1Token = await contract.l1Token()
if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) {
return false
}
// Make sure the L2 bridge matches.
const remoteL2Bridge = await contract.l2Bridge()
if (!hexStringEquals(remoteL2Bridge, this.l2Bridge.address)) {
return false
}
return true
} catch (err) {
// If the L2 token is not an L2StandardERC20, it may throw an error. If there's a call
// exception then we assume that the token is not supported. Other errors are thrown.
if (err.message.toString().includes('CALL_EXCEPTION')) {
return false
} else {
throw err
}
}
}
public async deposit(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
signer: Signer,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionResponse> {
return signer.sendTransaction(
await this.populateTransaction.deposit(l1Token, l2Token, amount, opts)
)
}
public async withdraw(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
signer: Signer,
opts?: {
overrides?: Overrides
}
): Promise<TransactionResponse> {
return signer.sendTransaction(
await this.populateTransaction.withdraw(l1Token, l2Token, amount, opts)
)
}
populateTransaction = {
deposit: async (
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionRequest> => {
if (!(await this.supportsTokenPair(l1Token, l2Token))) {
throw new Error(`token pair not supported by bridge`)
}
return this.l1Bridge.populateTransaction.depositERC20(
toAddress(l1Token),
toAddress(l2Token),
amount,
opts?.l2GasLimit || 200_000, // Default to 200k gas limit.
'0x', // No data.
opts?.overrides || {}
)
},
withdraw: async (
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionRequest> => {
if (!(await this.supportsTokenPair(l1Token, l2Token))) {
throw new Error(`token pair not supported by bridge`)
}
return this.l2Bridge.populateTransaction.withdraw(
toAddress(l2Token),
amount,
0, // L1 gas not required.
'0x', // No data.
opts?.overrides || {}
)
},
}
estimateGas = {
deposit: async (
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<BigNumber> => {
return this.messenger.l1Provider.estimateGas(
await this.populateTransaction.deposit(l1Token, l2Token, amount, opts)
)
},
withdraw: async (
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<BigNumber> => {
return this.messenger.l2Provider.estimateGas(
await this.populateTransaction.withdraw(l1Token, l2Token, amount, opts)
)
},
}
}
This diff is collapsed.
This diff is collapsed.
export * from './interfaces'
export * from './utils'
export * from './cross-chain-provider'
export * from './cross-chain-messenger'
export * from './adapters'
import { Overrides, Contract } from 'ethers'
import { Contract, Overrides, Signer, BigNumber } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
BlockTag,
} from '@ethersproject/abstract-provider'
import { NumberLike } from './types'
import {
NumberLike,
AddressLike,
MessageDirection,
TokenBridgeMessage,
} from './types'
import { ICrossChainMessenger } from './cross-chain-messenger'
/**
* Represents an L1<>L2 ERC20 token pair.
* Represents an adapter for an L1<>L2 token bridge. Each custom bridge currently needs its own
* adapter because the bridge interface is not standardized. This may change in the future.
*/
export interface ICrossChainERC20Pair {
export interface IBridgeAdapter {
/**
* Messenger that will be used to carry out cross-chain iteractions.
* Provider used to make queries related to cross-chain interactions.
*/
messenger: ICrossChainMessenger
/**
* Ethers Contract object connected to the L1 token.
* L1 bridge contract.
*/
l1Bridge: Contract
/**
* L2 bridge contract.
*/
l1Token: Contract
l2Bridge: Contract
/**
* Ethers Contract object connected to the L2 token.
* Finds all cross chain messages that correspond to token deposits or withdrawals sent by a
* particular address. Useful for finding deposits/withdrawals because the sender of the message
* will appear to be the StandardBridge contract and not the actual end user.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* find all messages in both directions.
* @returns All token bridge messages sent by the given address.
*/
l2Token: Contract
getTokenBridgeMessagesByAddress(
address: AddressLike,
opts?: {
direction?: MessageDirection
}
): Promise<TokenBridgeMessage[]>
/**
* Gets all deposits for a given address.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All deposit token bridge messages sent by the given address.
*/
getDepositsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]>
/**
* Gets all withdrawals for a given address.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All withdrawal token bridge messages sent by the given address.
*/
getWithdrawalsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]>
/**
* Checks whether the given token pair is supported by the bridge.
*
* @param l1Token The L1 token address.
* @param l2Token The L2 token address.
* @returns Whether the given token pair is supported by the bridge.
*/
supportsTokenPair(
l1Token: AddressLike,
l2Token: AddressLike
): Promise<boolean>
/**
* Deposits some tokens into the L2 chain.
*
* @param l1Token The L1 token address.
* @param l2Token The L2 token address.
* @param amount Amount of the token to deposit.
* @param signer Signer used to sign and send the transaction.
* @param opts Additional options.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the deposit transaction.
*/
deposit(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
signer: Signer,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
......@@ -46,13 +127,19 @@ export interface ICrossChainERC20Pair {
/**
* Withdraws some tokens back to the L1 chain.
*
* @param l1Token The L1 token address.
* @param l2Token The L2 token address.
* @param amount Amount of the token to withdraw.
* @param signer Signer used to sign and send the transaction.
* @param opts Additional options.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction response for the withdraw transaction.
*/
withdraw(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
signer: Signer,
opts?: {
overrides?: Overrides
}
......@@ -66,6 +153,8 @@ export interface ICrossChainERC20Pair {
/**
* Generates a transaction for depositing some tokens into the L2 chain.
*
* @param l1Token The L1 token address.
* @param l2Token The L2 token address.
* @param amount Amount of the token to deposit.
* @param opts Additional options.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
......@@ -73,22 +162,28 @@ export interface ICrossChainERC20Pair {
* @returns Transaction that can be signed and executed to deposit the tokens.
*/
deposit(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionResponse>
): Promise<TransactionRequest>
/**
* Generates a transaction for withdrawing some tokens back to the L1 chain.
*
* @param l1Token The L1 token address.
* @param l2Token The L2 token address.
* @param amount Amount of the token to withdraw.
* @param opts Additional options.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdraw(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
overrides?: Overrides
......@@ -104,33 +199,41 @@ export interface ICrossChainERC20Pair {
/**
* Estimates gas required to deposit some tokens into the L2 chain.
*
* @param l1Token The L1 token address.
* @param l2Token The L2 token address.
* @param amount Amount of the token to deposit.
* @param opts Additional options.
* @param opts.l2GasLimit Optional gas limit to use for the transaction on L2.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the tokens.
* @returns Gas estimate for the transaction.
*/
deposit(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionResponse>
): Promise<BigNumber>
/**
* Estimates gas required to withdraw some tokens back to the L1 chain.
*
* @param l1Token The L1 token address.
* @param l2Token The L2 token address.
* @param amount Amount of the token to withdraw.
* @param opts Additional options.
* @param opts.overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
* @returns Gas estimate for the transaction.
*/
withdraw(
l1Token: AddressLike,
l2Token: AddressLike,
amount: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionRequest>
): Promise<BigNumber>
}
}
This diff is collapsed.
export * from './cross-chain-erc20-pair'
export * from './bridge-adapter'
export * from './cross-chain-messenger'
export * from './cross-chain-provider'
export * from './l2-provider'
export * from './types'
This diff is collapsed.
export const DEPOSIT_CONFIRMATION_BLOCKS = {
// Mainnet
1: 50,
// Goerli
5: 12,
// Kovan
42: 12,
// Hardhat Local
// 2 just for testing purposes
31337: 2,
}
export const CHAIN_BLOCK_TIMES = {
// Mainnet
1: 13,
// Goerli
5: 15,
// Kovan
42: 4,
// Hardhat Local
31337: 1,
}
This diff is collapsed.
This diff is collapsed.
......@@ -3,3 +3,5 @@ export * from './contracts'
export * from './message-encoding'
export * from './type-utils'
export * from './misc-utils'
export * from './merkle-utils'
export * from './chain-constants'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment