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 .github
node_modules node_modules
.env
**/.env
test test
**/*_test.go **/*_test.go
......
This diff is collapsed.
...@@ -20,7 +20,7 @@ var ( ...@@ -20,7 +20,7 @@ var (
func main() { func main() {
// Set up logger with a default INFO level in case we fail to parse flags. // 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.Root().SetHandler(
log.LvlFilterHandler( log.LvlFilterHandler(
log.LvlInfo, log.LvlInfo,
......
...@@ -190,6 +190,7 @@ func NewConfig(ctx *cli.Context) (Config, error) { ...@@ -190,6 +190,7 @@ func NewConfig(ctx *cli.Context) (Config, error) {
SafeMinimumEtherBalance: ctx.GlobalUint64(flags.SafeMinimumEtherBalanceFlag.Name), SafeMinimumEtherBalance: ctx.GlobalUint64(flags.SafeMinimumEtherBalanceFlag.Name),
ClearPendingTxs: ctx.GlobalBool(flags.ClearPendingTxsFlag.Name), ClearPendingTxs: ctx.GlobalBool(flags.ClearPendingTxsFlag.Name),
/* Optional Flags */ /* Optional Flags */
LogLevel: ctx.GlobalString(flags.LogLevelFlag.Name),
SentryEnable: ctx.GlobalBool(flags.SentryEnableFlag.Name), SentryEnable: ctx.GlobalBool(flags.SentryEnableFlag.Name),
SentryDsn: ctx.GlobalString(flags.SentryDsnFlag.Name), SentryDsn: ctx.GlobalString(flags.SentryDsnFlag.Name),
SentryTraceRate: ctx.GlobalDuration(flags.SentryTraceRateFlag.Name), SentryTraceRate: ctx.GlobalDuration(flags.SentryTraceRateFlag.Name),
...@@ -217,10 +218,6 @@ func NewConfig(ctx *cli.Context) (Config, error) { ...@@ -217,10 +218,6 @@ func NewConfig(ctx *cli.Context) (Config, error) {
// ensure that it is well-formed. // ensure that it is well-formed.
func ValidateConfig(cfg *Config) error { func ValidateConfig(cfg *Config) error {
// Sanity check log level. // Sanity check log level.
if cfg.LogLevel == "" {
cfg.LogLevel = "debug"
}
_, err := log.LvlFromString(cfg.LogLevel) _, err := log.LvlFromString(cfg.LogLevel)
if err != nil { if err != nil {
return err return err
......
...@@ -9,9 +9,9 @@ import ( ...@@ -9,9 +9,9 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc" "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/bindings/scc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers" "github.com/ethereum-optimism/optimism/go/bss-core/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics" "github.com/ethereum-optimism/optimism/go/bss-core/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/log" "github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
......
...@@ -91,11 +91,10 @@ func GenSequencerBatchParams( ...@@ -91,11 +91,10 @@ func GenSequencerBatchParams(
// Iterate over the batch elements, grouping the elements according to // Iterate over the batch elements, grouping the elements according to
// the following critera: // the following critera:
// - All sequencer txs in the same group must have the same timestamp // - All txs in the same group must have the same timestamp.
// and block number. // - 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 // - If sequencer txs exist in a group, they must come before all
// queued txs. // queued txs.
// - A group should never split consecutive queued txs.
// //
// Assuming the block and timestamp criteria for sequencer txs are // Assuming the block and timestamp criteria for sequencer txs are
// respected within each group, the following are examples of groupings: // respected within each group, the following are examples of groupings:
...@@ -110,17 +109,18 @@ func GenSequencerBatchParams( ...@@ -110,17 +109,18 @@ func GenSequencerBatchParams(
// To enforce the above groupings, the following condition is // To enforce the above groupings, the following condition is
// used to determine when to create a new batch: // used to determine when to create a new batch:
// - On the first pass, or // - 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 tx was a queued tx, or
// - The preceding sequencer tx has a different // - The preceding sequencer tx has a different block number.
// block number/timestamp. // Note that a sequencer tx is usually required to create a new group,
// 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
// so a queued tx may ONLY exist as the first element in a group // is the very first element or it has a different timestamp from the
// if it is the very first element. // preceding tx.
needsNewGroupOnSequencerTx := !lastBlockIsSequencerTx || needsNewGroupOnSequencerTx := !lastBlockIsSequencerTx ||
el.Timestamp != lastTimestamp ||
el.BlockNumber != lastBlockNumber el.BlockNumber != lastBlockNumber
if len(groupedBlocks) == 0 || if len(groupedBlocks) == 0 ||
el.Timestamp != lastTimestamp ||
(el.IsSequencerTx() && needsNewGroupOnSequencerTx) { (el.IsSequencerTx() && needsNewGroupOnSequencerTx) {
groupedBlocks = append(groupedBlocks, groupedBlock{}) groupedBlocks = append(groupedBlocks, groupedBlock{})
......
...@@ -8,9 +8,9 @@ import ( ...@@ -8,9 +8,9 @@ import (
"strings" "strings"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc" "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/bss-core/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics" "github.com/ethereum-optimism/optimism/go/bss-core/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "github.com/ethereum-optimism/optimism/go/bss-core/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
......
...@@ -4,6 +4,7 @@ go 1.16 ...@@ -4,6 +4,7 @@ go 1.16
require ( require (
github.com/decred/dcrd/hdkeychain/v3 v3.0.0 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-optimism/optimism/l2geth v1.0.0
github.com/ethereum/go-ethereum v1.10.12 github.com/ethereum/go-ethereum v1.10.12
github.com/getsentry/sentry-go v0.11.0 github.com/getsentry/sentry-go v0.11.0
...@@ -14,3 +15,5 @@ require ( ...@@ -14,3 +15,5 @@ require (
) )
replace github.com/ethereum-optimism/optimism/l2geth => ../../l2geth 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 ...@@ -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/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/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.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/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/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= 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 ...@@ -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 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-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.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/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 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.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.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-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-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= 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 ...@@ -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/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/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 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 ...@@ -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.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.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.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/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.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.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.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/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.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/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 ...@@ -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-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-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-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/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-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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 ...@@ -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/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/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/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/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 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= 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= ...@@ -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.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.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.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.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-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 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 ( import (
"crypto/ecdsa" "crypto/ecdsa"
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/tyler-smith/go-bip39" "github.com/tyler-smith/go-bip39"
) )
...@@ -106,3 +107,69 @@ func ParsePrivateKeyStr(privKeyStr string) (*ecdsa.PrivateKey, error) { ...@@ -106,3 +107,69 @@ func ParsePrivateKeyStr(privKeyStr string) (*ecdsa.PrivateKey, error) {
hex := strings.TrimPrefix(privKeyStr, "0x") hex := strings.TrimPrefix(privKeyStr, "0x")
return crypto.HexToECDSA(hex) 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 ( import (
"bytes" "bytes"
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"strings" "strings"
"testing" "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/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -76,7 +76,7 @@ func TestParseAddress(t *testing.T) { ...@@ -76,7 +76,7 @@ func TestParseAddress(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { 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) require.Equal(t, err, test.expErr)
if test.expErr != nil { if test.expErr != nil {
return return
...@@ -141,7 +141,7 @@ func TestDerivePrivateKey(t *testing.T) { ...@@ -141,7 +141,7 @@ func TestDerivePrivateKey(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { 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) require.Equal(t, err, test.expErr)
if test.expErr != nil { if test.expErr != nil {
return return
...@@ -193,7 +193,7 @@ func TestParsePrivateKeyStr(t *testing.T) { ...@@ -193,7 +193,7 @@ func TestParsePrivateKeyStr(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { 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) require.Equal(t, err, test.expErr)
if test.expErr != nil { if test.expErr != nil {
return return
...@@ -246,20 +246,20 @@ func TestGetConfiguredPrivateKey(t *testing.T) { ...@@ -246,20 +246,20 @@ func TestGetConfiguredPrivateKey(t *testing.T) {
mnemonic: validMnemonic, mnemonic: validMnemonic,
hdPath: validHDPath, hdPath: validHDPath,
privKeyStr: validPrivKeyStr, privKeyStr: validPrivKeyStr,
expErr: batchsubmitter.ErrCannotGetPrivateKey, expErr: bsscore.ErrCannotGetPrivateKey,
}, },
{ {
name: "neither menmonic+hdpath or privkey", name: "neither menmonic+hdpath or privkey",
mnemonic: "", mnemonic: "",
hdPath: "", hdPath: "",
privKeyStr: "", privKeyStr: "",
expErr: batchsubmitter.ErrCannotGetPrivateKey, expErr: bsscore.ErrCannotGetPrivateKey,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
privKey, err := batchsubmitter.GetConfiguredPrivateKey( privKey, err := bsscore.GetConfiguredPrivateKey(
test.mnemonic, test.hdPath, test.privKeyStr, test.mnemonic, test.hdPath, test.privKeyStr,
) )
require.Equal(t, err, test.expErr) 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 ( ...@@ -7,7 +7,7 @@ import (
"math/big" "math/big"
"strings" "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/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
......
...@@ -9,8 +9,8 @@ import ( ...@@ -9,8 +9,8 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers" "github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/mock" "github.com/ethereum-optimism/optimism/go/bss-core/mock"
"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/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "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 ( import (
"errors" "errors"
"io" "io"
"time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
...@@ -36,3 +37,14 @@ func SentryStreamHandler(wr io.Writer, fmtr log.Format) log.Handler { ...@@ -36,3 +37,14 @@ func SentryStreamHandler(wr io.Writer, fmtr log.Format) log.Handler {
}) })
return log.LazyHandler(log.SyncHandler(h)) 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 ( import (
"bytes" "bytes"
...@@ -7,8 +7,8 @@ import ( ...@@ -7,8 +7,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics" "github.com/ethereum-optimism/optimism/go/bss-core/metrics"
"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/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"testing" "testing"
"time" "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/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require" "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 @@ ...@@ -31,9 +31,10 @@
"@eth-optimism/contracts": "0.5.11", "@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5", "@eth-optimism/core-utils": "0.7.5",
"@eth-optimism/message-relayer": "0.2.15", "@eth-optimism/message-relayer": "0.2.15",
"@eth-optimism/sdk": "0.0.7",
"@ethersproject/abstract-provider": "^5.5.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.5.3",
"@ethersproject/transactions": "^5.4.0", "@ethersproject/transactions": "^5.5.0",
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^4.4.0", "@openzeppelin/contracts": "^4.4.0",
...@@ -62,7 +63,7 @@ ...@@ -62,7 +63,7 @@
"eslint-plugin-react": "^7.24.0", "eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1", "eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.3.0", "ethereum-waffle": "^3.3.0",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"hardhat": "^2.3.0", "hardhat": "^2.3.0",
"hardhat-gas-reporter": "^1.0.4", "hardhat-gas-reporter": "^1.0.4",
"lint-staged": "11.0.0", "lint-staged": "11.0.0",
......
/* Imports: External */ /* Imports: External */
import { Contract, ContractFactory } from 'ethers' import { Contract, ContractFactory } from 'ethers'
import { ethers } from 'hardhat' 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 */ /* Imports: Internal */
import { expect } from './shared/setup' import { expect } from './shared/setup'
import { Direction } from './shared/watcher-utils'
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
import { import {
DEFAULT_TEST_GAS_L1, DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2, DEFAULT_TEST_GAS_L2,
envConfig, envConfig,
sleep,
withdrawalTest, withdrawalTest,
} from './shared/utils' } from './shared/utils'
...@@ -56,23 +59,35 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -56,23 +59,35 @@ describe('Basic L1<>L2 Communication', async () => {
const value = `0x${'77'.repeat(32)}` const value = `0x${'77'.repeat(32)}`
// Send L2 -> L1 message. // Send L2 -> L1 message.
const transaction = await env.l2Messenger.sendMessage( const transaction = await env.messenger.sendMessage(
L1SimpleStorage.address, {
L1SimpleStorage.interface.encodeFunctionData('setValue', [value]), direction: MessageDirection.L2_TO_L1,
5000000, target: L1SimpleStorage.address,
message: L1SimpleStorage.interface.encodeFunctionData('setValue', [
value,
]),
},
{ {
gasLimit: DEFAULT_TEST_GAS_L2, overrides: {
gasLimit: DEFAULT_TEST_GAS_L2,
},
} }
) )
await transaction.wait()
await env.relayXDomainMessages(transaction) let status: MessageStatus
await env.waitForXDomainTransaction(transaction, Direction.L2ToL1) 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( expect(await L1SimpleStorage.msgSender()).to.equal(
env.l1Messenger.address env.messenger.contracts.l1.L1CrossDomainMessenger.address
) )
expect(await L1SimpleStorage.xDomainSender()).to.equal( expect(await L1SimpleStorage.xDomainSender()).to.equal(
env.l2Wallet.address await env.messenger.l2Signer.getAddress()
) )
expect(await L1SimpleStorage.value()).to.equal(value) expect(await L1SimpleStorage.value()).to.equal(value)
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(1) expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(1)
...@@ -85,25 +100,36 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -85,25 +100,36 @@ describe('Basic L1<>L2 Communication', async () => {
const value = `0x${'42'.repeat(32)}` const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message. // Send L1 -> L2 message.
const transaction = await env.l1Messenger.sendMessage( const transaction = await env.messenger.sendMessage(
L2SimpleStorage.address, {
L2SimpleStorage.interface.encodeFunctionData('setValue', [value]), direction: MessageDirection.L1_TO_L2,
5000000, target: L2SimpleStorage.address,
message: L2SimpleStorage.interface.encodeFunctionData('setValue', [
value,
]),
},
{ {
gasLimit: DEFAULT_TEST_GAS_L1, 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( expect(await L2SimpleStorage.msgSender()).to.equal(
env.l2Messenger.address env.messenger.contracts.l2.L2CrossDomainMessenger.address
) )
expect(await L2SimpleStorage.txOrigin()).to.equal( expect(await L2SimpleStorage.txOrigin()).to.equal(
applyL1ToL2Alias(env.l1Messenger.address) applyL1ToL2Alias(
env.messenger.contracts.l1.L1CrossDomainMessenger.address
)
) )
expect(await L2SimpleStorage.xDomainSender()).to.equal( expect(await L2SimpleStorage.xDomainSender()).to.equal(
env.l1Wallet.address await env.messenger.l1Signer.getAddress()
) )
expect(await L2SimpleStorage.value()).to.equal(value) expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1) expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
...@@ -117,9 +143,10 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -117,9 +143,10 @@ describe('Basic L1<>L2 Communication', async () => {
const value = `0x${'42'.repeat(32)}` const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message. // Send L1 -> L2 message.
const tx = await env.ctc const tx =
.connect(env.l1Wallet) await env.messenger.contracts.l1.CanonicalTransactionChain.connect(
.enqueue( env.messenger.l1Signer
).enqueue(
L2SimpleStorage.address, L2SimpleStorage.address,
5000000, 5000000,
L2SimpleStorage.interface.encodeFunctionData('setValueNotXDomain', [ L2SimpleStorage.interface.encodeFunctionData('setValueNotXDomain', [
...@@ -129,11 +156,12 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -129,11 +156,12 @@ describe('Basic L1<>L2 Communication', async () => {
gasLimit: DEFAULT_TEST_GAS_L1, gasLimit: DEFAULT_TEST_GAS_L1,
} }
) )
const receipt = await tx.wait() const receipt = await tx.wait()
const waitUntilBlock = const waitUntilBlock =
receipt.blockNumber + envConfig.DTL_ENQUEUE_CONFIRMATIONS receipt.blockNumber + envConfig.DTL_ENQUEUE_CONFIRMATIONS
let currBlock = await env.l1Provider.getBlockNumber() let currBlock = await env.messenger.l1Provider.getBlockNumber()
while (currBlock <= waitUntilBlock) { while (currBlock <= waitUntilBlock) {
const progress = const progress =
envConfig.DTL_ENQUEUE_CONFIRMATIONS - (waitUntilBlock - currBlock) envConfig.DTL_ENQUEUE_CONFIRMATIONS - (waitUntilBlock - currBlock)
...@@ -141,66 +169,28 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -141,66 +169,28 @@ describe('Basic L1<>L2 Communication', async () => {
`Waiting for ${progress}/${envConfig.DTL_ENQUEUE_CONFIRMATIONS} confirmations.` `Waiting for ${progress}/${envConfig.DTL_ENQUEUE_CONFIRMATIONS} confirmations.`
) )
await sleep(5000) await sleep(5000)
currBlock = await env.l1Provider.getBlockNumber() currBlock = await env.messenger.l1Provider.getBlockNumber()
} }
console.log('Enqueue should be confirmed.') console.log('Enqueue should be confirmed.')
await awaitCondition( await awaitCondition(
async () => { async () => {
const sender = await L2SimpleStorage.msgSender() const sender = await L2SimpleStorage.msgSender()
return sender === env.l1Wallet.address return sender === (await env.messenger.l1Signer.getAddress())
}, },
2000, 2000,
60 60
) )
// No aliasing when an EOA goes directly to L2. // No aliasing when an EOA goes directly to L2.
expect(await L2SimpleStorage.msgSender()).to.equal(env.l1Wallet.address) expect(await L2SimpleStorage.msgSender()).to.equal(
expect(await L2SimpleStorage.txOrigin()).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)
})
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.txOrigin()).to.equal(
const { remoteReceipt } = await env.waitForXDomainTransaction( await env.messenger.l1Signer.getAddress()
transaction,
Direction.L1ToL2
) )
expect(await L2SimpleStorage.value()).to.equal(value)
expect(remoteReceipt.status).to.equal(0) expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
}) })
}) })
}) })
...@@ -127,4 +127,58 @@ describe('Bridged tokens', () => { ...@@ -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 { ...@@ -11,7 +11,6 @@ import {
DEFAULT_TEST_GAS_L1, DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2, DEFAULT_TEST_GAS_L2,
envConfig, envConfig,
PROXY_SEQUENCER_ENTRYPOINT_ADDRESS,
withdrawalTest, withdrawalTest,
} from './shared/utils' } from './shared/utils'
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
...@@ -31,9 +30,6 @@ describe('Native ETH Integration Tests', async () => { ...@@ -31,9 +30,6 @@ describe('Native ETH Integration Tests', async () => {
const l1BobBalance = await l1Bob.getBalance() const l1BobBalance = await l1Bob.getBalance()
const l2BobBalance = await l2Bob.getBalance() const l2BobBalance = await l2Bob.getBalance()
const sequencerBalance = await _env.ovmEth.balanceOf(
PROXY_SEQUENCER_ENTRYPOINT_ADDRESS
)
const l1BridgeBalance = await _env.l1Wallet.provider.getBalance( const l1BridgeBalance = await _env.l1Wallet.provider.getBalance(
_env.l1Bridge.address _env.l1Bridge.address
) )
...@@ -44,7 +40,6 @@ describe('Native ETH Integration Tests', async () => { ...@@ -44,7 +40,6 @@ describe('Native ETH Integration Tests', async () => {
l1BobBalance, l1BobBalance,
l2BobBalance, l2BobBalance,
l1BridgeBalance, l1BridgeBalance,
sequencerBalance,
} }
} }
......
...@@ -4,6 +4,7 @@ import { TransactionResponse } from '@ethersproject/providers' ...@@ -4,6 +4,7 @@ import { TransactionResponse } from '@ethersproject/providers'
import { getContractFactory, predeploys } from '@eth-optimism/contracts' import { getContractFactory, predeploys } from '@eth-optimism/contracts'
import { Watcher } from '@eth-optimism/core-utils' import { Watcher } from '@eth-optimism/core-utils'
import { getMessagesAndProofsForL2Transaction } from '@eth-optimism/message-relayer' import { getMessagesAndProofsForL2Transaction } from '@eth-optimism/message-relayer'
import { CrossChainMessenger } from '@eth-optimism/sdk'
/* Imports: Internal */ /* Imports: Internal */
import { import {
...@@ -11,6 +12,7 @@ import { ...@@ -11,6 +12,7 @@ import {
l1Provider, l1Provider,
l2Provider, l2Provider,
replicaProvider, replicaProvider,
verifierProvider,
l1Wallet, l1Wallet,
l2Wallet, l2Wallet,
gasPriceOracleWallet, gasPriceOracleWallet,
...@@ -54,9 +56,11 @@ export class OptimismEnv { ...@@ -54,9 +56,11 @@ export class OptimismEnv {
l2Wallet: Wallet l2Wallet: Wallet
// The providers // The providers
messenger: CrossChainMessenger
l1Provider: providers.JsonRpcProvider l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider l2Provider: providers.JsonRpcProvider
replicaProvider: providers.JsonRpcProvider replicaProvider: providers.JsonRpcProvider
verifierProvider: providers.JsonRpcProvider
constructor(args: any) { constructor(args: any) {
this.addressManager = args.addressManager this.addressManager = args.addressManager
...@@ -71,9 +75,11 @@ export class OptimismEnv { ...@@ -71,9 +75,11 @@ export class OptimismEnv {
this.watcher = args.watcher this.watcher = args.watcher
this.l1Wallet = args.l1Wallet this.l1Wallet = args.l1Wallet
this.l2Wallet = args.l2Wallet this.l2Wallet = args.l2Wallet
this.messenger = args.messenger
this.l1Provider = args.l1Provider this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider this.l2Provider = args.l2Provider
this.replicaProvider = args.replicaProvider this.replicaProvider = args.replicaProvider
this.verifierProvider = args.verifierProvider
this.ctc = args.ctc this.ctc = args.ctc
this.scc = args.scc this.scc = args.scc
} }
...@@ -123,6 +129,13 @@ export class OptimismEnv { ...@@ -123,6 +129,13 @@ export class OptimismEnv {
.connect(l2Wallet) .connect(l2Wallet)
.attach(predeploys.OVM_L1BlockNumber) .attach(predeploys.OVM_L1BlockNumber)
const network = await l1Provider.getNetwork()
const messenger = new CrossChainMessenger({
l1SignerOrProvider: l1Wallet,
l2SignerOrProvider: l2Wallet,
l1ChainId: network.chainId,
})
return new OptimismEnv({ return new OptimismEnv({
addressManager, addressManager,
l1Bridge, l1Bridge,
...@@ -138,8 +151,10 @@ export class OptimismEnv { ...@@ -138,8 +151,10 @@ export class OptimismEnv {
watcher, watcher,
l1Wallet, l1Wallet,
l2Wallet, l2Wallet,
messenger,
l1Provider, l1Provider,
l2Provider, l2Provider,
verifierProvider,
replicaProvider, replicaProvider,
}) })
} }
......
...@@ -62,6 +62,8 @@ const procEnv = cleanEnv(process.env, { ...@@ -62,6 +62,8 @@ const procEnv = cleanEnv(process.env, {
REPLICA_URL: str({ default: 'http://localhost:8549' }), REPLICA_URL: str({ default: 'http://localhost:8549' }),
REPLICA_POLLING_INTERVAL: num({ default: 10 }), REPLICA_POLLING_INTERVAL: num({ default: 10 }),
VERIFIER_URL: str({ default: 'http://localhost:8547' }),
PRIVATE_KEY: str({ PRIVATE_KEY: str({
default: default:
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
...@@ -93,8 +95,8 @@ const procEnv = cleanEnv(process.env, { ...@@ -93,8 +95,8 @@ const procEnv = cleanEnv(process.env, {
RUN_STRESS_TESTS: bool({ RUN_STRESS_TESTS: bool({
default: true, default: true,
}), }),
RUN_NIGHTLY_TESTS: bool({ RUN_VERIFIER_TESTS: bool({
default: false, default: true,
}), }),
MOCHA_TIMEOUT: num({ MOCHA_TIMEOUT: num({
...@@ -121,6 +123,11 @@ export const replicaProvider = injectL2Context( ...@@ -121,6 +123,11 @@ export const replicaProvider = injectL2Context(
) )
replicaProvider.pollingInterval = procEnv.REPLICA_POLLING_INTERVAL 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 // The sequencer private key which is funded on L1
export const l1Wallet = new Wallet(procEnv.PRIVATE_KEY, l1Provider) export const l1Wallet = new Wallet(procEnv.PRIVATE_KEY, l1Provider)
...@@ -135,8 +142,6 @@ export const gasPriceOracleWallet = new Wallet( ...@@ -135,8 +142,6 @@ export const gasPriceOracleWallet = new Wallet(
) )
// Predeploys // Predeploys
export const PROXY_SEQUENCER_ENTRYPOINT_ADDRESS =
'0x4200000000000000000000000000000000000004'
export const OVM_ETH_ADDRESS = predeploys.OVM_ETH export const OVM_ETH_ADDRESS = predeploys.OVM_ETH
export const L2_CHAINID = procEnv.L2_CHAINID export const L2_CHAINID = procEnv.L2_CHAINID
...@@ -210,7 +215,7 @@ export const conditionalTest = ( ...@@ -210,7 +215,7 @@ export const conditionalTest = (
} }
await fn() await fn()
}).timeout(timeout || envConfig.MOCHA_TIMEOUT) }).timeout(timeout || envConfig.MOCHA_TIMEOUT * 2)
} }
export const withdrawalTest = (name, fn, timeout?: number) => 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 ...@@ -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 can be configured using either environment variables or passed at runtime as
flags. 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: To compile the code, run:
``` ```
...@@ -25,46 +25,39 @@ $ make geth ...@@ -25,46 +25,39 @@ $ make geth
### Running a Sequencer ### Running a Sequencer
Running a sequencer requires the [Data Transport Layer](https://github.com/ethereum-optimism/data-transport-layer) Running a sequencer that ingests L1 to L2 transactions requires running the
to be synced. The data transport layer is responsible for indexing transactions [Data Transport Layer](https://github.com/ethereum-optimism/data-transport-layer).
from Layer One concurrently. The sequencer pulls in transactions from the data The data transport layer is responsible for indexing transactions
transport layer and executes them. The URL of the data transport layer should be 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`. used for the sequencer config option `--rollup.clienthttp`.
See the script `scripts/start.sh`. It sets many of the config options The `scripts` directory contains some scripts that make it easy to run a
and accepts CLI flags. For usage, run the command: 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 ```bash
$ ./scripts/start.sh -h $ ./scripts/init.sh
``` ```
This may be suitable for simple usecases, users that need more flexibility This script can be ran with the `DEVELOPMENT` env var set which will add
with their configuration can run the command: 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 ```bash
$ USING_OVM=true ./build/bin/geth \ $ ./scripts/start.sh
--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
``` ```
To persist the database, pass the `--datadir` with a path to the directory for This script can be modified to work with `dlv` by prefixing the `$cmd`
the database to be persisted in. Without this flag, an in memory database will with `dlv exec` and being sure to prefix the `geth` arguments with `--`
be used. To tune the log level, use the `--verbosity` flag with an integer. so they are interpreted as arguments to `geth` instead of `dlv`.
### Running a Verifier ### Running a Verifier
......
...@@ -51,7 +51,6 @@ import ( ...@@ -51,7 +51,6 @@ import (
) )
var ( var (
errOVMUnsupported = errors.New("OVM: Unsupported RPC Method")
errNoSequencerURL = errors.New("sequencer transaction forwarding not configured") errNoSequencerURL = errors.New("sequencer transaction forwarding not configured")
) )
...@@ -386,6 +385,19 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs ...@@ -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) log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
return common.Hash{}, 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) return SubmitTransaction(ctx, s.b, signed)
} }
...@@ -1619,9 +1631,6 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c ...@@ -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 // SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool. // transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { 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 // Look up the wallet containing the requested signer
account := accounts.Account{Address: args.From} account := accounts.Account{Address: args.From}
...@@ -1654,9 +1663,6 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen ...@@ -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, // FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction,
// and returns it to the caller for further processing (signing + broadcast) // and returns it to the caller for further processing (signing + broadcast)
func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) { 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 // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err return nil, err
...@@ -1714,9 +1720,6 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod ...@@ -1714,9 +1720,6 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
// //
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { 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 // Look up the wallet containing the requested signer
account := accounts.Account{Address: addr} account := accounts.Account{Address: addr}
...@@ -1742,9 +1745,6 @@ type SignTransactionResult struct { ...@@ -1742,9 +1745,6 @@ type SignTransactionResult struct {
// The node needs to have the private key of the account corresponding with // The node needs to have the private key of the account corresponding with
// the given from address and it needs to be unlocked. // the given from address and it needs to be unlocked.
func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) { func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
if rcfg.UsingOVM {
return nil, errOVMUnsupported
}
if args.Gas == nil { if args.Gas == nil {
return nil, fmt.Errorf("gas not specified") return nil, fmt.Errorf("gas not specified")
} }
......
...@@ -931,18 +931,7 @@ func (w *worker) commitNewTx(tx *types.Transaction) error { ...@@ -931,18 +931,7 @@ func (w *worker) commitNewTx(tx *types.Transaction) error {
// Preserve liveliness as best as possible. Must panic on L1 to L2 // Preserve liveliness as best as possible. Must panic on L1 to L2
// transactions as the timestamp cannot be malleated // transactions as the timestamp cannot be malleated
if parent.Time() > tx.L1Timestamp() { if parent.Time() > tx.L1Timestamp() {
log.Error("Monotonicity violation", "index", num) log.Error("Monotonicity violation", "index", num, "parent", parent.Time(), "tx", tx.L1Timestamp())
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")
}
} }
// Fill in the index field in the tx meta if it is `nil`. // 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 { ...@@ -825,13 +825,14 @@ func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error {
if now.Sub(current) > s.timestampRefreshThreshold { if now.Sub(current) > s.timestampRefreshThreshold {
current = now current = now
} }
log.Info("Updating latest timestamp", "timestamp", current, "unix", current.Unix())
tx.SetL1Timestamp(uint64(current.Unix())) tx.SetL1Timestamp(uint64(current.Unix()))
} else if tx.L1Timestamp() == 0 && s.verifier { } else if tx.L1Timestamp() == 0 && s.verifier {
// This should never happen // This should never happen
log.Error("No tx timestamp found when running as verifier", "hash", tx.Hash().Hex()) 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 // 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() l1BlockNumber := tx.L1BlockNumber()
......
...@@ -136,8 +136,9 @@ services: ...@@ -136,8 +136,9 @@ services:
- l1_chain - l1_chain
- deployer - deployer
- dtl - dtl
- l2geth
deploy: deploy:
replicas: 0 replicas: 1
build: build:
context: .. context: ..
dockerfile: ./ops/docker/Dockerfile.geth dockerfile: ./ops/docker/Dockerfile.geth
...@@ -146,6 +147,7 @@ services: ...@@ -146,6 +147,7 @@ services:
- ./envs/geth.env - ./envs/geth.env
environment: environment:
ETH1_HTTP: http://l1_chain:8545 ETH1_HTTP: http://l1_chain:8545
SEQUENCER_CLIENT_HTTP: http://l2geth:8545
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
ROLLUP_CLIENT_HTTP: http://dtl:7878 ROLLUP_CLIENT_HTTP: http://dtl:7878
ROLLUP_BACKEND: 'l1' 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 RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash
COPY ./l2geth /l2geth COPY ./l2geth /l2geth
COPY ./go/bss-core /go/bss-core
COPY ./go/batch-submitter/go.mod ./go/batch-submitter/go.sum /go/batch-submitter/ COPY ./go/batch-submitter/go.mod ./go/batch-submitter/go.sum /go/batch-submitter/
WORKDIR /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/ ./ COPY ./go/batch-submitter/ ./
RUN make RUN make
FROM alpine:3.15 FROM alpine:3.13
RUN apk add --no-cache ca-certificates jq curl RUN apk add --no-cache ca-certificates jq curl
COPY --from=builder /go/batch-submitter/batch-submitter /usr/local/bin/ COPY --from=builder /go/batch-submitter/batch-submitter /usr/local/bin/
......
...@@ -12,6 +12,9 @@ COPY --from=builder /optimism/*.json /optimism/yarn.lock ./ ...@@ -12,6 +12,9 @@ COPY --from=builder /optimism/*.json /optimism/yarn.lock ./
COPY --from=builder /optimism/node_modules ./node_modules COPY --from=builder /optimism/node_modules ./node_modules
# copy deps (would have been nice if docker followed the symlinks required) # 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/package.json ./packages/core-utils/package.json
COPY --from=builder /optimism/packages/core-utils/dist ./packages/core-utils/dist 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 ...@@ -13,6 +13,7 @@ RUN apt-get update -y && apt-get install -y git curl jq python3
# us to cache the installation steps # us to cache the installation steps
WORKDIR /opt/optimism WORKDIR /opt/optimism
COPY *.json yarn.lock ./ 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/core-utils/package.json ./packages/core-utils/package.json
COPY packages/common-ts/package.json ./packages/common-ts/package.json COPY packages/common-ts/package.json ./packages/common-ts/package.json
COPY packages/contracts/package.json ./packages/contracts/package.json COPY packages/contracts/package.json ./packages/contracts/package.json
......
...@@ -9,6 +9,7 @@ DATA_TRANSPORT_LAYER__LOGS_PER_POLLING_INTERVAL=2000 ...@@ -9,6 +9,7 @@ DATA_TRANSPORT_LAYER__LOGS_PER_POLLING_INTERVAL=2000
DATA_TRANSPORT_LAYER__DANGEROUSLY_CATCH_ALL_ERRORS=true DATA_TRANSPORT_LAYER__DANGEROUSLY_CATCH_ALL_ERRORS=true
DATA_TRANSPORT_LAYER__SERVER_HOSTNAME=0.0.0.0 DATA_TRANSPORT_LAYER__SERVER_HOSTNAME=0.0.0.0
DATA_TRANSPORT_LAYER__L1_START_HEIGHT=1 DATA_TRANSPORT_LAYER__L1_START_HEIGHT=1
DATA_TRANSPORT_LAYER__BSS_HARDFORK_1_INDEX=0
DATA_TRANSPORT_LAYER__ADDRESS_MANAGER= DATA_TRANSPORT_LAYER__ADDRESS_MANAGER=
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT= DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT=
......
...@@ -37,13 +37,13 @@ ...@@ -37,13 +37,13 @@
"@eth-optimism/contracts": "0.5.11", "@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5", "@eth-optimism/core-utils": "0.7.5",
"@eth-optimism/ynatm": "^0.2.2", "@eth-optimism/ynatm": "^0.2.2",
"@ethersproject/abstract-provider": "^5.4.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.5.3",
"@sentry/node": "^6.3.1", "@sentry/node": "^6.3.1",
"bcfg": "^0.1.6", "bcfg": "^0.1.6",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"old-contracts": "npm:@eth-optimism/contracts@^0.0.2-alpha.7", "old-contracts": "npm:@eth-optimism/contracts@^0.0.2-alpha.7",
"prom-client": "^13.1.0" "prom-client": "^13.1.0"
}, },
......
ignores: [ ignores: [
"@codechecks/client", "@codechecks/client",
"@ethersproject/transactions",
"@openzeppelin/contracts", "@openzeppelin/contracts",
"@openzeppelin/contracts-upgradeable", "@openzeppelin/contracts-upgradeable",
"@typechain/ethers-v5", "@typechain/ethers-v5",
...@@ -8,4 +7,4 @@ ignores: [ ...@@ -8,4 +7,4 @@ ignores: [
"solhint-plugin-prettier", "solhint-plugin-prettier",
"ts-generator", "ts-generator",
"yargs", "yargs",
] ]
\ No newline at end of file
...@@ -59,15 +59,14 @@ ...@@ -59,15 +59,14 @@
}, },
"dependencies": { "dependencies": {
"@eth-optimism/core-utils": "0.7.5", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.4.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abstract-signer": "^5.4.1", "@ethersproject/abstract-signer": "^5.5.0",
"@ethersproject/hardware-wallets": "^5.4.0" "@ethersproject/hardware-wallets": "^5.5.0"
}, },
"devDependencies": { "devDependencies": {
"@codechecks/client": "^0.1.11", "@codechecks/client": "^0.1.11",
"@defi-wonderland/smock": "^2.0.2", "@defi-wonderland/smock": "^2.0.2",
"@eth-optimism/smock": "1.1.10", "@eth-optimism/smock": "1.1.10",
"@ethersproject/transactions": "^5.4.0",
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.6", "@nomiclabs/hardhat-etherscan": "^2.1.6",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
...@@ -96,7 +95,7 @@ ...@@ -96,7 +95,7 @@
"eslint-plugin-react": "^7.24.0", "eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1", "eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.3.0", "ethereum-waffle": "^3.3.0",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"glob": "^7.1.6", "glob": "^7.1.6",
"hardhat": "^2.3.0", "hardhat": "^2.3.0",
"hardhat-deploy": "^0.9.3", "hardhat-deploy": "^0.9.3",
...@@ -123,6 +122,6 @@ ...@@ -123,6 +122,6 @@
"yargs": "^16.2.0" "yargs": "^16.2.0"
}, },
"peerDependencies": { "peerDependencies": {
"ethers": "^5.4.5" "ethers": "^5"
} }
} }
...@@ -32,12 +32,12 @@ ...@@ -32,12 +32,12 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"dependencies": { "dependencies": {
"@ethersproject/abstract-provider": "^5.4.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/bytes": "^5.5.0", "@ethersproject/bytes": "^5.5.0",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.5.3",
"@ethersproject/web": "^5.5.0", "@ethersproject/web": "^5.5.1",
"chai": "^4.3.4", "chai": "^4.3.4",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },
"devDependencies": { "devDependencies": {
......
...@@ -39,8 +39,8 @@ ...@@ -39,8 +39,8 @@
"@eth-optimism/common-ts": "0.2.1", "@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.11", "@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.5.3",
"@ethersproject/transactions": "^5.4.0", "@ethersproject/transactions": "^5.5.0",
"@sentry/node": "^6.3.1", "@sentry/node": "^6.3.1",
"@sentry/tracing": "^6.3.1", "@sentry/tracing": "^6.3.1",
"@types/express": "^4.17.12", "@types/express": "^4.17.12",
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
"browser-or-node": "^1.3.0", "browser-or-node": "^1.3.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"express": "^4.17.1", "express": "^4.17.1",
"express-prom-bundle": "^6.3.6", "express-prom-bundle": "^6.3.6",
"level": "^6.0.1", "level": "^6.0.1",
...@@ -58,7 +58,6 @@ ...@@ -58,7 +58,6 @@
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
}, },
"devDependencies": { "devDependencies": {
"@ethersproject/abstract-provider": "^5.4.1",
"@types/browser-or-node": "^1.3.0", "@types/browser-or-node": "^1.3.0",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
"@types/chai-as-promised": "^7.1.4", "@types/chai-as-promised": "^7.1.4",
......
...@@ -31,11 +31,17 @@ interface Indexed { ...@@ -31,11 +31,17 @@ interface Indexed {
index: number index: number
} }
interface ExtraTransportDBOptions {
bssHardfork1Index?: number
}
export class TransportDB { export class TransportDB {
public db: SimpleDB public db: SimpleDB
public opts: ExtraTransportDBOptions
constructor(leveldb: LevelUp) { constructor(leveldb: LevelUp, opts?: ExtraTransportDBOptions) {
this.db = new SimpleDB(leveldb) this.db = new SimpleDB(leveldb)
this.opts = opts || {}
} }
public async putEnqueueEntries(entries: EnqueueEntry[]): Promise<void> { public async putEnqueueEntries(entries: EnqueueEntry[]): Promise<void> {
...@@ -254,26 +260,7 @@ export class TransportDB { ...@@ -254,26 +260,7 @@ export class TransportDB {
return null return null
} }
if (transaction.queueOrigin === 'l1') { return this._makeFullTransaction(transaction)
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
}
} }
public async getLatestFullTransaction(): Promise<TransactionEntry> { public async getLatestFullTransaction(): Promise<TransactionEntry> {
...@@ -293,31 +280,46 @@ export class TransportDB { ...@@ -293,31 +280,46 @@ export class TransportDB {
const fullTransactions = [] const fullTransactions = []
for (const transaction of transactions) { for (const transaction of transactions) {
if (transaction.queueOrigin === 'l1') { fullTransactions.push(await this._makeFullTransaction(transaction))
const enqueue = await this.getEnqueueByIndex(transaction.queueIndex)
if (enqueue === null) {
return null
}
fullTransactions.push({
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp: enqueue.timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
})
} else {
fullTransactions.push(transaction)
}
} }
return fullTransactions 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
}
let timestamp = enqueue.timestamp
if (
typeof this.opts.bssHardfork1Index === 'number' &&
transaction.index >= this.opts.bssHardfork1Index
) {
timestamp = transaction.timestamp
}
return {
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
}
}
private async _getLatestEntryIndex(key: string): Promise<number> { private async _getLatestEntryIndex(key: string): Promise<number> {
return this.db.get<number>(`${key}:latest`, 0) || 0 return this.db.get<number>(`${key}:latest`, 0) || 0
} }
......
...@@ -18,7 +18,7 @@ import { ...@@ -18,7 +18,7 @@ import {
TransactionEntry, TransactionEntry,
EventHandlerSet, EventHandlerSet,
} from '../../../types' } from '../../../types'
import { SEQUENCER_GAS_LIMIT, parseSignatureVParam } from '../../../utils' import { parseSignatureVParam } from '../../../utils'
export const handleEventsSequencerBatchAppended: EventHandlerSet< export const handleEventsSequencerBatchAppended: EventHandlerSet<
SequencerBatchAppendedEvent, SequencerBatchAppendedEvent,
...@@ -65,7 +65,6 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet< ...@@ -65,7 +65,6 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
submitter: l1Transaction.from, submitter: l1Transaction.from,
l1TransactionHash: l1Transaction.hash, l1TransactionHash: l1Transaction.hash,
l1TransactionData: l1Transaction.data, l1TransactionData: l1Transaction.data,
gasLimit: `${SEQUENCER_GAS_LIMIT}`,
prevTotalElements: batchSubmissionEvent.args._prevTotalElements, prevTotalElements: batchSubmissionEvent.args._prevTotalElements,
batchIndex: batchSubmissionEvent.args._batchIndex, batchIndex: batchSubmissionEvent.args._batchIndex,
...@@ -143,7 +142,7 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet< ...@@ -143,7 +142,7 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
.toNumber(), .toNumber(),
batchIndex: extraData.batchIndex.toNumber(), batchIndex: extraData.batchIndex.toNumber(),
blockNumber: BigNumber.from(0).toNumber(), blockNumber: BigNumber.from(0).toNumber(),
timestamp: BigNumber.from(0).toNumber(), timestamp: context.timestamp,
gasLimit: BigNumber.from(0).toString(), gasLimit: BigNumber.from(0).toString(),
target: constants.AddressZero, target: constants.AddressZero,
origin: constants.AddressZero, origin: constants.AddressZero,
......
...@@ -104,7 +104,9 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> { ...@@ -104,7 +104,9 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> {
} = {} as any } = {} as any
protected async _init(): Promise<void> { 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) this.l1IngestionMetrics = registerMetrics(this.metrics)
......
...@@ -84,7 +84,9 @@ export class L2IngestionService extends BaseService<L2IngestionServiceOptions> { ...@@ -84,7 +84,9 @@ export class L2IngestionService extends BaseService<L2IngestionServiceOptions> {
this.l2IngestionMetrics = registerMetrics(this.metrics) 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 = this.state.l2RpcProvider =
typeof this.options.l2RpcProvider === 'string' typeof this.options.l2RpcProvider === 'string'
......
...@@ -36,6 +36,7 @@ export interface L1DataTransportServiceOptions { ...@@ -36,6 +36,7 @@ export interface L1DataTransportServiceOptions {
defaultBackend: string defaultBackend: string
l1GasPriceBackend: string l1GasPriceBackend: string
l1StartHeight?: number l1StartHeight?: number
bssHardfork1Index?: number
} }
const optionSettings = { const optionSettings = {
......
...@@ -51,6 +51,7 @@ type ethNetwork = 'mainnet' | 'kovan' | 'goerli' ...@@ -51,6 +51,7 @@ type ethNetwork = 'mainnet' | 'kovan' | 'goerli'
useSentry: config.bool('use-sentry', false), useSentry: config.bool('use-sentry', false),
sentryDsn: config.str('sentry-dsn'), sentryDsn: config.str('sentry-dsn'),
sentryTraceRate: config.ufloat('sentry-trace-rate', 0.05), sentryTraceRate: config.ufloat('sentry-trace-rate', 0.05),
bssHardfork1Index: config.uint('bss-hardfork-1-index', null),
}) })
const stop = async (signal) => { const stop = async (signal) => {
......
...@@ -87,7 +87,10 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> { ...@@ -87,7 +87,10 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> {
await this.options.db.open() 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 = this.state.l1RpcProvider =
typeof this.options.l1RpcProvider === 'string' typeof this.options.l1RpcProvider === 'string'
? new JsonRpcProvider(this.options.l1RpcProvider) ? new JsonRpcProvider(this.options.l1RpcProvider)
......
...@@ -42,7 +42,6 @@ export interface SequencerBatchAppendedExtraData { ...@@ -42,7 +42,6 @@ export interface SequencerBatchAppendedExtraData {
submitter: string submitter: string
l1TransactionData: string l1TransactionData: string
l1TransactionHash: string l1TransactionHash: string
gasLimit: string
// Stuff from TransactionBatchAppended. // Stuff from TransactionBatchAppended.
prevTotalElements: BigNumber 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 './common'
export * from './constants'
export * from './contracts' export * from './contracts'
export * from './validation' export * from './validation'
export * from './eth-tx' export * from './eth-tx'
/* Imports: External */ /* Imports: External */
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
import { Block } from '@ethersproject/abstract-provider'
/* Imports: Internal */ /* Imports: Internal */
import { expect } from '../../../../setup' import { expect } from '../../../../setup'
...@@ -18,7 +17,7 @@ describe('Event Handlers: CanonicalTransactionChain.StateBatchAppended', () => { ...@@ -18,7 +17,7 @@ describe('Event Handlers: CanonicalTransactionChain.StateBatchAppended', () => {
data: l1StateBatchData, data: l1StateBatchData,
} }
// Source: https://etherscan.io/block/12106615 // Source: https://etherscan.io/block/12106615
const eventBlock: Block = { const eventBlock = {
timestamp: 1616680530, timestamp: 1616680530,
number: 12106615, number: 12106615,
hash: '0x9c40310e19e943ad38e170329465c4489f6aba5895e9cacdac236be181aea31f', hash: '0x9c40310e19e943ad38e170329465c4489f6aba5895e9cacdac236be181aea31f',
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
"@sentry/node": "^6.3.1", "@sentry/node": "^6.3.1",
"bcfg": "^0.1.6", "bcfg": "^0.1.6",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"merkletreejs": "^0.2.18", "merkletreejs": "^0.2.18",
"rlp": "^2.2.6" "rlp": "^2.2.6"
}, },
......
...@@ -33,11 +33,11 @@ ...@@ -33,11 +33,11 @@
"devDependencies": { "devDependencies": {
"@discoveryjs/json-ext": "^0.5.3", "@discoveryjs/json-ext": "^0.5.3",
"@eth-optimism/core-utils": "0.7.5", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abi": "^5.5.0", "@ethersproject/abi": "^5.5.0",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/bignumber": "^5.5.0", "@ethersproject/bignumber": "^5.5.0",
"@ethersproject/properties": "^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": "^15.12.2",
"@types/node-fetch": "^3.0.3", "@types/node-fetch": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/eslint-plugin": "^4.26.0",
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
"eslint-plugin-unicorn": "^32.0.1", "eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.4.0", "ethereum-waffle": "^3.4.0",
"ethereumjs-util": "^7.1.3", "ethereumjs-util": "^7.1.3",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"lint-staged": "11.0.0", "lint-staged": "11.0.0",
"mocha": "^9.1.2", "mocha": "^9.1.2",
"node-fetch": "2.6.7", "node-fetch": "2.6.7",
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
"@eth-optimism/common-ts": "0.2.1", "@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/core-utils": "0.7.5", "@eth-optimism/core-utils": "0.7.5",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"ethers": "^5.4.5", "ethers": "^5.5.4",
"express": "^4.17.1", "express": "^4.17.1",
"express-prom-bundle": "^6.3.6", "express-prom-bundle": "^6.3.6",
"lint-staged": "11.0.0", "lint-staged": "11.0.0",
......
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
"url": "https://github.com/ethereum-optimism/optimism-monorepo.git" "url": "https://github.com/ethereum-optimism/optimism-monorepo.git"
}, },
"devDependencies": { "devDependencies": {
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abstract-signer": "^5.5.0",
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
...@@ -50,6 +52,7 @@ ...@@ -50,6 +52,7 @@
"eslint-plugin-react": "^7.24.0", "eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1", "eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.4.0", "ethereum-waffle": "^3.4.0",
"ethers": "^5.5.4",
"hardhat": "^2.3.0", "hardhat": "^2.3.0",
"lint-staged": "11.0.0", "lint-staged": "11.0.0",
"mocha": "^8.4.0", "mocha": "^8.4.0",
...@@ -61,8 +64,10 @@ ...@@ -61,8 +64,10 @@
"dependencies": { "dependencies": {
"@eth-optimism/contracts": "0.5.11", "@eth-optimism/contracts": "0.5.11",
"@eth-optimism/core-utils": "0.7.5", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.5.1", "merkletreejs": "^0.2.27",
"@ethersproject/abstract-signer": "^5.5.0", "rlp": "^2.2.7"
"ethers": "^5.5.2" },
"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 './interfaces'
export * from './utils' export * from './utils'
export * from './cross-chain-provider'
export * from './cross-chain-messenger' export * from './cross-chain-messenger'
export * from './adapters'
This diff is collapsed.
export * from './cross-chain-erc20-pair' export * from './bridge-adapter'
export * from './cross-chain-messenger' export * from './cross-chain-messenger'
export * from './cross-chain-provider'
export * from './l2-provider' export * from './l2-provider'
export * from './types' export * from './types'
...@@ -6,6 +6,9 @@ import { ...@@ -6,6 +6,9 @@ import {
import { Signer } from '@ethersproject/abstract-signer' import { Signer } from '@ethersproject/abstract-signer'
import { Contract, BigNumber } from 'ethers' import { Contract, BigNumber } from 'ethers'
import { ICrossChainMessenger } from './cross-chain-messenger'
import { IBridgeAdapter } from './bridge-adapter'
/** /**
* L1 contract references. * L1 contract references.
*/ */
...@@ -68,27 +71,25 @@ export interface OEContractsLike { ...@@ -68,27 +71,25 @@ export interface OEContractsLike {
} }
/** /**
* Represents list of custom bridges. * Something that looks like the list of custom bridges.
*/ */
export interface CustomBridges { export interface BridgeAdapterData {
l1: { [name: string]: {
[name: string]: Contract Adapter: new (opts: {
} messenger: ICrossChainMessenger
l2: { l1Bridge: AddressLike
[name: string]: Contract l2Bridge: AddressLike
}) => IBridgeAdapter
l1Bridge: AddressLike
l2Bridge: AddressLike
} }
} }
/** /**
* Something that looks like the list of custom bridges. * Something that looks like the list of custom bridges.
*/ */
export interface CustomBridgesLike { export interface BridgeAdapters {
l1: { [name: string]: IBridgeAdapter
[K in keyof CustomBridges['l1']]: AddressLike
}
l2: {
[K in keyof CustomBridges['l2']]: AddressLike
}
} }
/** /**
...@@ -215,9 +216,9 @@ export interface StateRootBatchHeader { ...@@ -215,9 +216,9 @@ export interface StateRootBatchHeader {
* Information about a state root, including header, block number, and root iself. * Information about a state root, including header, block number, and root iself.
*/ */
export interface StateRoot { export interface StateRoot {
blockNumber: number
header: StateRootBatchHeader
stateRoot: string stateRoot: string
stateRootIndexInBatch: number
batch: StateRootBatch
} }
/** /**
...@@ -229,6 +230,20 @@ export interface StateRootBatch { ...@@ -229,6 +230,20 @@ export interface StateRootBatch {
stateRoots: string[] stateRoots: string[]
} }
/**
* Proof data required to finalize an L2 to L1 message.
*/
export interface CrossChainMessageProof {
stateRoot: string
stateRootBatchHeader: StateRootBatchHeader
stateRootProof: {
index: number
siblings: string[]
}
stateTrieWitness: string
storageTrieWitness: string
}
/** /**
* Stuff that can be coerced into a transaction. * Stuff that can be coerced into a transaction.
*/ */
......
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' ...@@ -3,3 +3,5 @@ export * from './contracts'
export * from './message-encoding' export * from './message-encoding'
export * from './type-utils' export * from './type-utils'
export * from './misc-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