Commit fc791962 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into refactor_safe_attributes

parents 04f02c9a f8500c64
---
'@eth-optimism/sdk': patch
---
Update migrated withdrawal gaslimit calculation
---
'@eth-optimism/atst': minor
---
Fix main and module in atst package.json
---
'@eth-optimism/fault-detector': patch
---
Fixes a bug that would cause the fault detector to error out if no outputs had been proposed yet.
......@@ -538,7 +538,7 @@ jobs:
command: |
# Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional
# constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building.
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=true OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=false OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \
--format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \
-- -timeout=20m ./...
working_directory: <<parameters.module>>
......@@ -849,7 +849,7 @@ jobs:
./hive \
-sim=<<parameters.sim>> \
-sim.loglevel=5 \
-client=go-ethereum,op-geth_optimism-history,op-proposer_<<parameters.version>>,op-batcher_<<parameters.version>>,op-node_<<parameters.version>> |& tee /tmp/hive.log || echo "failed."
-client=go-ethereum,op-geth_optimism,op-proposer_<<parameters.version>>,op-batcher_<<parameters.version>>,op-node_<<parameters.version>> |& tee /tmp/hive.log || echo "failed."
- run:
command: |
tar -cvf /tmp/workspace.tgz -C /home/circleci/project /home/circleci/project/workspace
......
This diff is collapsed.
This diff is collapsed.
......@@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
var (
......@@ -82,8 +83,7 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com
return nil, fmt.Errorf("cannot abi encode relayMessage: %w", err)
}
// Set the outer gas limit. This cannot be zero
gasLimit := uint64(len(data)*16 + 200_000)
gasLimit := MigrateWithdrawalGasLimit(data)
w := NewWithdrawal(
versionedNonce,
......@@ -95,3 +95,25 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com
)
return w, nil
}
func MigrateWithdrawalGasLimit(data []byte) uint64 {
// Compute the cost of the calldata
dataCost := uint64(0)
for _, b := range data {
if b == 0 {
dataCost += params.TxDataZeroGas
} else {
dataCost += params.TxDataNonZeroGasEIP2028
}
}
// Set the outer gas limit. This cannot be zero
gasLimit := dataCost + 200_000
// Cap the gas limit to be 25 million to prevent creating withdrawals
// that go over the block gas limit.
if gasLimit > 25_000_000 {
gasLimit = 25_000_000
}
return gasLimit
}
......@@ -2,6 +2,7 @@ package crossdomain_test
import (
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
......@@ -11,6 +12,8 @@ import (
"github.com/stretchr/testify/require"
)
var big25Million = big.NewInt(25_000_000)
func TestMigrateWithdrawal(t *testing.T) {
withdrawals := make([]*crossdomain.LegacyWithdrawal, 0)
......@@ -31,6 +34,57 @@ func TestMigrateWithdrawal(t *testing.T) {
require.Equal(t, legacy.XDomainNonce.Uint64(), withdrawal.Nonce.Uint64())
require.Equal(t, *withdrawal.Sender, predeploys.L2CrossDomainMessengerAddr)
require.Equal(t, *withdrawal.Target, l1CrossDomainMessenger)
// Always equal to or lower than the cap
require.True(t, withdrawal.GasLimit.Cmp(big25Million) <= 0)
})
}
}
// TestMigrateWithdrawalGasLimitMax computes the migrated withdrawal
// gas limit with a very large amount of data. The max value for a migrated
// withdrawal's gas limit is 25 million.
func TestMigrateWithdrawalGasLimitMax(t *testing.T) {
size := 300_000_000 / 16
data := make([]byte, size)
for _, i := range data {
data[i] = 0xff
}
result := crossdomain.MigrateWithdrawalGasLimit(data)
require.Equal(t, result, big25Million.Uint64())
}
// TestMigrateWithdrawalGasLimit tests an assortment of zero and non zero
// bytes when computing the migrated withdrawal's gas limit.
func TestMigrateWithdrawalGasLimit(t *testing.T) {
tests := []struct {
input []byte
output uint64
}{
{
input: []byte{},
output: 200_000,
},
{
input: []byte{0xff},
output: 200_000 + 16,
},
{
input: []byte{0xff, 0x00},
output: 200_000 + 16 + 4,
},
{
input: []byte{0x00},
output: 200_000 + 4,
},
{
input: []byte{0x00, 0x00, 0x00},
output: 200_000 + 4 + 4 + 4,
},
}
for _, test := range tests {
result := crossdomain.MigrateWithdrawalGasLimit(test.input)
require.Equal(t, test.output, result)
}
}
......@@ -12,6 +12,7 @@ import (
"time"
bss "github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/sources"
l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
......@@ -311,7 +312,9 @@ func TestMigration(t *testing.T) {
},
L1EpochPollInterval: 4 * time.Second,
}
rollupNode, err := node.New(ctx, rollupNodeConfig, log.New(), snapLog, "", metrics.NewMetrics(""))
rollupLog := log.New()
rollupNodeConfig.Rollup.LogDescription(rollupLog, chaincfg.L2ChainIDToNetworkName)
rollupNode, err := node.New(ctx, rollupNodeConfig, rollupLog, snapLog, "", metrics.NewMetrics(""))
require.NoError(t, err)
require.NoError(t, rollupNode.Start(ctx))
......
......@@ -26,6 +26,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics"
rollupNode "github.com/ethereum-optimism/optimism/op-node/node"
......@@ -468,6 +469,8 @@ func (cfg SystemConfig) Start() (*System, error) {
}
}
c.Rollup.LogDescription(cfg.Loggers[name], chaincfg.L2ChainIDToNetworkName)
node, err := rollupNode.New(context.Background(), &c, cfg.Loggers[name], snapLog, "", metrics.NewMetrics(""))
if err != nil {
didErrAfterStart = true
......
......@@ -415,6 +415,7 @@ func TestMixedDepositValidity(t *testing.T) {
// TestMixedWithdrawalValidity makes a number of withdrawal transactions and ensures ones with modified parameters are
// rejected while unmodified ones are accepted. This runs test cases in different systems.
func TestMixedWithdrawalValidity(t *testing.T) {
parallel(t)
// Setup our logger handler
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
......@@ -424,7 +425,6 @@ func TestMixedWithdrawalValidity(t *testing.T) {
for i := 0; i <= 8; i++ {
i := i // avoid loop var capture
t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) {
parallel(t)
// Create our system configuration, funding all accounts we created for L1/L2, and start it
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 6
......
# Batch Decoding Tool
The batch decoding tool is a utility to aid in debugging the batch submitter & the op-node
by looking at what batches were submitted on L1.
## Design Philosophy
The `batch_decoder` tool is designed to be simple & flexible. It offloads as much data analysis
as possible to other tools. It is built around manipulating JSON on disk. The first stage is to
fetch all transaction which are sent to a batch inbox address. Those transactions are decoded into
frames in that step & information about them is recorded. After transactions are fetched the frames
are re-assembled into channels in a second step that does not touch the network.
## Commands
### Fetch
`batch_decoder fetch` pulls all L1 transactions sent to the batch inbox address in a given L1 block
range and then stores them on disk to a specified path as JSON files where the name of the file is
the transaction hash.
### Reassemble
`batch_decoder reassemble` goes through all of the found frames in the cache & then turns them
into channels. It then stores the channels with metadata on disk where the file name is the Channel ID.
## JQ Cheat Sheet
`jq` is a really useful utility for manipulating JSON files.
```
# Pretty print a JSON file
jq . $JSON_FILE
# Print the number of valid & invalid transactions
jq .valid_data $TX_DIR/* | sort | uniq -c
# Select all transactions that have invalid data & then print the transaction hash
jq "select(.valid_data == false)|.tx.hash" $TX_DIR
# Select all channels that are not ready and then get the id and inclusion block & tx hash of the first frame.
jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].transaction_hash]" $CHANNEL_DIR
```
## Roadmap
- Parallel transaction fetching (CLI-3563)
- Create force-close channel tx data from channel ID (CLI-3564)
- Pull the batches out of channels & store that information inside the ChannelWithMetadata (CLI-3565)
- Transaction Bytes used
- Total uncompressed (different from tx bytes) + compressed bytes
- Invert ChannelWithMetadata so block numbers/hashes are mapped to channels they are submitted in (CLI-3560)
......@@ -16,11 +16,12 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
)
type TransactionWithMeta struct {
type TransactionWithMetadata struct {
TxIndex uint64 `json:"tx_index"`
InboxAddr common.Address `json:"inbox_address"`
BlockNumber uint64 `json:"block_number"`
BlockHash common.Hash `json:"block_hash"`
BlockTime uint64 `json:"block_time"`
ChainId uint64 `json:"chain_id"`
Sender common.Address `json:"sender"`
ValidSender bool `json:"valid_sender"`
......@@ -38,6 +39,9 @@ type Config struct {
OutDirectory string
}
// Batches fetches & stores all transactions sent to the batch inbox address in
// the given block range (inclusive to exclusive).
// The transactions & metadata are written to the out directory.
func Batches(client *ethclient.Client, config Config) (totalValid, totalInvalid int) {
if err := os.MkdirAll(config.OutDirectory, 0750); err != nil {
log.Fatal(err)
......@@ -53,13 +57,15 @@ func Batches(client *ethclient.Client, config Config) (totalValid, totalInvalid
return
}
// fetchBatchesPerBlock gets a block & the parses all of the transactions in the block.
func fetchBatchesPerBlock(client *ethclient.Client, number *big.Int, signer types.Signer, config Config) (validBatchCount, invalidBatchCount int) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
block, err := client.BlockByNumber(ctx, number)
if err != nil {
log.Fatal(err)
}
fmt.Println("Fetched block: ", number)
for i, tx := range block.Transactions() {
if tx.To() != nil && *tx.To() == config.BatchInbox {
sender, err := signer.Sender(tx)
......@@ -88,13 +94,14 @@ func fetchBatchesPerBlock(client *ethclient.Client, number *big.Int, signer type
invalidBatchCount += 1
}
txm := &TransactionWithMeta{
txm := &TransactionWithMetadata{
Tx: tx,
Sender: sender,
ValidSender: validSender,
TxIndex: uint64(i),
BlockNumber: block.NumberU64(),
BlockHash: block.Hash(),
BlockTime: block.Time(),
ChainId: config.ChainID.Uint64(),
InboxAddr: config.BatchInbox,
Frames: frames,
......
......@@ -8,6 +8,7 @@ import (
"time"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/reassemble"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
......@@ -59,7 +60,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
chainID, err := client.ChainID(ctx)
if err != nil {
......@@ -82,6 +83,36 @@ func main() {
return nil
},
},
{
Name: "reassemble",
Usage: "Reassembles channels from fetched batches",
Flags: []cli.Flag{
cli.StringFlag{
Name: "inbox",
Value: "0xff00000000000000000000000000000000000420",
Usage: "Batch Inbox Address",
},
cli.StringFlag{
Name: "in",
Value: "/tmp/batch_decoder/transactions_cache",
Usage: "Cache directory for the found transactions",
},
cli.StringFlag{
Name: "out",
Value: "/tmp/batch_decoder/channel_cache",
Usage: "Cache directory for the found channels",
},
},
Action: func(cliCtx *cli.Context) error {
config := reassemble.Config{
BatchInbox: common.HexToAddress(cliCtx.String("inbox")),
InDirectory: cliCtx.String("in"),
OutDirectory: cliCtx.String("out"),
}
reassemble.Channels(config)
return nil
},
},
}
if err := app.Run(os.Args); err != nil {
......
package reassemble
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"path"
"sort"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
)
type ChannelWithMetadata struct {
ID derive.ChannelID `json:"id"`
IsReady bool `json:"is_ready"`
InvalidFrames bool `json:"invalid_frames"`
InvalidBatches bool `json:"invalid_batches"`
Frames []FrameWithMetadata `json:"frames"`
Batches []derive.BatchV1 `json:"batches"`
}
type FrameWithMetadata struct {
TxHash common.Hash `json:"transaction_hash"`
InclusionBlock uint64 `json:"inclusion_block"`
Timestamp uint64 `json:"timestamp"`
BlockHash common.Hash `json:"block_hash"`
Frame derive.Frame `json:"frame"`
}
type Config struct {
BatchInbox common.Address
InDirectory string
OutDirectory string
}
// Channels loads all transactions from the given input directory that are submitted to the
// specified batch inbox and then re-assembles all channels & writes the re-assembled channels
// to the out directory.
func Channels(config Config) {
if err := os.MkdirAll(config.OutDirectory, 0750); err != nil {
log.Fatal(err)
}
txns := loadTransactions(config.InDirectory, config.BatchInbox)
// Sort first by block number then by transaction index inside the block number range.
// This is to match the order they are processed in derivation.
sort.Slice(txns, func(i, j int) bool {
if txns[i].BlockNumber == txns[j].BlockNumber {
return txns[i].TxIndex < txns[j].TxIndex
} else {
return txns[i].BlockNumber < txns[j].BlockNumber
}
})
frames := transactionsToFrames(txns)
framesByChannel := make(map[derive.ChannelID][]FrameWithMetadata)
for _, frame := range frames {
framesByChannel[frame.Frame.ID] = append(framesByChannel[frame.Frame.ID], frame)
}
for id, frames := range framesByChannel {
ch := processFrames(id, frames)
filename := path.Join(config.OutDirectory, fmt.Sprintf("%s.json", id.String()))
if err := writeChannel(ch, filename); err != nil {
log.Fatal(err)
}
}
}
func writeChannel(ch ChannelWithMetadata, filename string) error {
file, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
enc := json.NewEncoder(file)
return enc.Encode(ch)
}
func processFrames(id derive.ChannelID, frames []FrameWithMetadata) ChannelWithMetadata {
ch := derive.NewChannel(id, eth.L1BlockRef{Number: frames[0].InclusionBlock})
invalidFrame := false
for _, frame := range frames {
if ch.IsReady() {
fmt.Printf("Channel %v is ready despite having more frames\n", id.String())
invalidFrame = true
break
}
if err := ch.AddFrame(frame.Frame, eth.L1BlockRef{Number: frame.InclusionBlock}); err != nil {
fmt.Printf("Error adding to channel %v. Err: %v\n", id.String(), err)
invalidFrame = true
}
}
var batches []derive.BatchV1
invalidBatches := false
if ch.IsReady() {
br, err := derive.BatchReader(ch.Reader(), eth.L1BlockRef{})
if err == nil {
for batch, err := br(); err != io.EOF; batch, err = br() {
if err != nil {
fmt.Printf("Error reading batch for channel %v. Err: %v\n", id.String(), err)
invalidBatches = true
} else {
batches = append(batches, batch.Batch.BatchV1)
}
}
} else {
fmt.Printf("Error creating batch reader for channel %v. Err: %v\n", id.String(), err)
}
} else {
fmt.Printf("Channel %v is not ready\n", id.String())
}
return ChannelWithMetadata{
ID: id,
Frames: frames,
IsReady: ch.IsReady(),
InvalidFrames: invalidFrame,
InvalidBatches: invalidBatches,
Batches: batches,
}
}
func transactionsToFrames(txns []fetch.TransactionWithMetadata) []FrameWithMetadata {
var out []FrameWithMetadata
for _, tx := range txns {
for _, frame := range tx.Frames {
fm := FrameWithMetadata{
TxHash: tx.Tx.Hash(),
InclusionBlock: tx.BlockNumber,
BlockHash: tx.BlockHash,
Timestamp: tx.BlockTime,
Frame: frame,
}
out = append(out, fm)
}
}
return out
}
func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithMetadata {
files, err := os.ReadDir(dir)
if err != nil {
log.Fatal(err)
}
var out []fetch.TransactionWithMetadata
for _, file := range files {
f := path.Join(dir, file.Name())
txm := loadTransactionsFile(f)
if txm.InboxAddr == inbox && txm.ValidSender {
out = append(out, txm)
}
}
return out
}
func loadTransactionsFile(file string) fetch.TransactionWithMetadata {
f, err := os.Open(file)
if err != nil {
log.Fatal(err)
}
defer f.Close()
dec := json.NewDecoder(f)
var txm fetch.TransactionWithMetadata
if err := dec.Decode(&txm); err != nil {
log.Fatalf("Failed to decode %v. Err: %v\n", file, err)
}
return txm
}
......@@ -8,6 +8,7 @@ import (
"strconv"
"syscall"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/cmd/doc"
"github.com/urfave/cli"
......@@ -22,6 +23,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/node"
"github.com/ethereum-optimism/optimism/op-node/version"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
)
......@@ -48,12 +50,7 @@ var VersionWithMeta = func() string {
func main() {
// Set up logger with a default INFO level in case we fail to parse flags,
// otherwise the final critical log won't show what the parsing error was.
log.Root().SetHandler(
log.LvlFilterHandler(
log.LvlInfo,
log.StreamHandler(os.Stdout, log.TerminalFormat(true)),
),
)
oplog.SetupDefaults()
app := cli.NewApp()
app.Version = VersionWithMeta
......@@ -85,12 +82,12 @@ func main() {
func RollupNodeMain(ctx *cli.Context) error {
log.Info("Initializing Rollup Node")
logCfg, err := opnode.NewLogConfig(ctx)
if err != nil {
logCfg := oplog.ReadCLIConfig(ctx)
if err := logCfg.Check(); err != nil {
log.Error("Unable to create the log config", "error", err)
return err
}
log := logCfg.NewLogger()
log := oplog.NewLogger(logCfg)
m := metrics.NewMetrics("default")
cfg, err := opnode.NewConfig(ctx, log)
......@@ -104,6 +101,13 @@ func RollupNodeMain(ctx *cli.Context) error {
return err
}
// Only pretty-print the banner if it is a terminal log. Other log it as key-value pairs.
if logCfg.Format == "terminal" {
log.Info("rollup config:\n" + cfg.Rollup.Description(chaincfg.L2ChainIDToNetworkName))
} else {
cfg.Rollup.LogDescription(log, chaincfg.L2ChainIDToNetworkName)
}
n, err := node.New(context.Background(), cfg, log, snapshotLog, VersionWithMeta, m)
if err != nil {
log.Error("Unable to create the rollup node", "error", err)
......
package node
import (
"fmt"
"os"
"strings"
"golang.org/x/term"
"github.com/ethereum/go-ethereum/log"
)
type LogConfig struct {
Level string // Log level: trace, debug, info, warn, error, crit. Capitals are accepted too.
Color bool // Color the log output. Defaults to true if terminal is detected.
Format string // Format the log output. Supported formats: 'text', 'json'
}
func DefaultLogConfig() LogConfig {
return LogConfig{
Level: "info",
Format: "text",
Color: term.IsTerminal(int(os.Stdout.Fd())),
}
}
// Check verifes that the LogConfig is valid. Calls to `NewLogger` may fail if this
// returns an error.
func (cfg *LogConfig) Check() error {
switch cfg.Format {
case "json", "json-pretty", "terminal", "text":
default:
return fmt.Errorf("unrecognized log format: %s", cfg.Format)
}
level := strings.ToLower(cfg.Level) // ignore case
_, err := log.LvlFromString(level)
if err != nil {
return fmt.Errorf("unrecognized log level: %w", err)
}
return nil
}
// NewLogger creates a logger based on the supplied configuration
func (cfg *LogConfig) NewLogger() log.Logger {
handler := log.StreamHandler(os.Stdout, format(cfg.Format, cfg.Color))
handler = log.SyncHandler(handler)
handler = log.LvlFilterHandler(level(cfg.Level), handler)
logger := log.New()
logger.SetHandler(handler)
return logger
}
// format turns a string and color into a structured Format object
func format(lf string, color bool) log.Format {
switch lf {
case "json":
return log.JSONFormat()
case "json-pretty":
return log.JSONFormatEx(true, true)
case "text", "terminal":
return log.TerminalFormat(color)
default:
panic("Failed to create `log.Format` from options")
}
}
// level parses the level string into an appropriate object
func level(s string) log.Lvl {
s = strings.ToLower(s) // ignore case
l, err := log.LvlFromString(s)
if err != nil {
panic(fmt.Sprintf("Could not parse log level: %v", err))
}
return l
}
......@@ -13,7 +13,6 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics"
......@@ -62,8 +61,6 @@ func New(ctx context.Context, cfg *Config, log log.Logger, snapshotLog log.Logge
// not a context leak, gossipsub is closed with a context.
n.resourcesCtx, n.resourcesClose = context.WithCancel(context.Background())
log.Info("rollup config:\n" + cfg.Rollup.Description(chaincfg.L2ChainIDToNetworkName))
err := n.init(ctx, cfg, snapshotLog)
if err != nil {
log.Error("Error initializing the rollup node", "err", err)
......
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/eth"
......@@ -271,6 +272,28 @@ func (c *Config) Description(l2Chains map[string]string) string {
return banner
}
// Description outputs a banner describing the important parts of rollup configuration in a log format.
// Optionally provide a mapping of L2 chain IDs to network names to label the L2 chain with if not unknown.
// The config should be config.Check()-ed before creating a description.
func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) {
// Find and report the network the user is running
networkL2 := ""
if l2Chains != nil {
networkL2 = l2Chains[c.L2ChainID.String()]
}
if networkL2 == "" {
networkL2 = "unknown L2"
}
networkL1 := params.NetworkNames[c.L1ChainID.String()]
if networkL1 == "" {
networkL1 = "unknown L1"
}
log.Info("Rollup Config", "l2_chain_id", c.L2ChainID, "l2_network", networkL2, "l1_chain_id", c.L1ChainID,
"l1_network", networkL1, "l2_start_time", c.Genesis.L2Time, "l2_block_hash", c.Genesis.L2.Hash.String(),
"l2_block_number", c.Genesis.L2.Number, "l1_block_hash", c.Genesis.L1.Hash.String(),
"l1_block_number", c.Genesis.L1.Number, "regolith_time", fmtForkTimeOrUnset(c.RegolithTime))
}
func fmtForkTimeOrUnset(v *uint64) string {
if v == nil {
return "(not configured)"
......
......@@ -168,21 +168,6 @@ func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) {
return &rollupConfig, nil
}
// NewLogConfig creates a log config from the provided flags or environment variables.
func NewLogConfig(ctx *cli.Context) (node.LogConfig, error) {
cfg := node.DefaultLogConfig() // Done to set color based on terminal type
cfg.Level = ctx.GlobalString(flags.LogLevelFlag.Name)
cfg.Format = ctx.GlobalString(flags.LogFormatFlag.Name)
if ctx.IsSet(flags.LogColorFlag.Name) {
cfg.Color = ctx.GlobalBool(flags.LogColorFlag.Name)
}
if err := cfg.Check(); err != nil {
return cfg, err
}
return cfg, nil
}
func NewSnapshotLogger(ctx *cli.Context) (log.Logger, error) {
snapshotFile := ctx.GlobalString(flags.SnapshotLog.Name)
handler := log.DiscardHandler()
......
......@@ -28,13 +28,13 @@ func CLIFlags(envPrefix string) []cli.Flag {
},
cli.StringFlag{
Name: FormatFlagName,
Usage: "Format the log output. Supported formats: 'text', 'json'",
Usage: "Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty',",
Value: "text",
EnvVar: opservice.PrefixEnvVar(envPrefix, "LOG_FORMAT"),
},
cli.BoolFlag{
Name: ColorFlagName,
Usage: "Color the log output",
Usage: "Color the log output if in terminal mode",
EnvVar: opservice.PrefixEnvVar(envPrefix, "LOG_COLOR"),
},
}
......@@ -43,12 +43,12 @@ func CLIFlags(envPrefix string) []cli.Flag {
type CLIConfig struct {
Level string // Log level: trace, debug, info, warn, error, crit. Capitals are accepted too.
Color bool // Color the log output. Defaults to true if terminal is detected.
Format string // Format the log output. Supported formats: 'text', 'json'
Format string // Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty'
}
func (cfg CLIConfig) Check() error {
switch cfg.Format {
case "json", "json-pretty", "terminal", "text":
case "json", "json-pretty", "terminal", "text", "logfmt":
default:
return fmt.Errorf("unrecognized log format: %s", cfg.Format)
}
......@@ -85,7 +85,9 @@ func ReadLocalCLIConfig(ctx *cli.Context) CLIConfig {
cfg := DefaultCLIConfig()
cfg.Level = ctx.String(LevelFlagName)
cfg.Format = ctx.String(FormatFlagName)
cfg.Color = ctx.Bool(ColorFlagName)
if ctx.IsSet(ColorFlagName) {
cfg.Color = ctx.Bool(ColorFlagName)
}
return cfg
}
......@@ -93,7 +95,9 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig {
cfg := DefaultCLIConfig()
cfg.Level = ctx.GlobalString(LevelFlagName)
cfg.Format = ctx.GlobalString(FormatFlagName)
cfg.Color = ctx.GlobalBool(ColorFlagName)
if ctx.IsSet(ColorFlagName) {
cfg.Color = ctx.GlobalBool(ColorFlagName)
}
return cfg
}
......@@ -104,8 +108,16 @@ func Format(lf string, color bool) log.Format {
return log.JSONFormat()
case "json-pretty":
return log.JSONFormatEx(true, true)
case "text", "terminal":
case "text":
if term.IsTerminal(int(os.Stdout.Fd())) {
return log.TerminalFormat(color)
} else {
return log.LogfmtFormat()
}
case "terminal":
return log.TerminalFormat(color)
case "logfmt":
return log.LogfmtFormat()
default:
panic("Failed to create `log.Format` from options")
}
......
......@@ -10,7 +10,7 @@ func SetupDefaults() {
log.Root().SetHandler(
log.LvlFilterHandler(
log.LvlInfo,
log.StreamHandler(os.Stdout, log.TerminalFormat(true)),
log.StreamHandler(os.Stdout, log.LogfmtFormat()),
),
)
}
......@@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
......@@ -316,6 +317,7 @@ func StoragePatch(patch io.Reader, address common.Address) HeadFn {
}
type OvmOwnersConfig struct {
Network string `json:"network"`
Owner common.Address `json:"owner"`
Sequencer common.Address `json:"sequencer"`
Proposer common.Address `json:"proposer"`
......@@ -323,17 +325,42 @@ type OvmOwnersConfig struct {
func OvmOwners(conf *OvmOwnersConfig) HeadFn {
return func(headState *state.StateDB) error {
var addressManager common.Address // Lib_AddressManager
var l1SBProxy common.Address // Proxy__OVM_L1StandardBridge
var l1XDMProxy common.Address // Proxy__OVM_L1CrossDomainMessenger
var l1ERC721BridgeProxy common.Address
switch conf.Network {
case "mainnet":
addressManager = common.HexToAddress("0xdE1FCfB0851916CA5101820A69b13a4E276bd81F")
l1SBProxy = common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1")
l1XDMProxy = common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1")
l1ERC721BridgeProxy = common.HexToAddress("0x5a7749f83b81B301cAb5f48EB8516B986DAef23D")
case "goerli":
addressManager = common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111")
l1SBProxy = common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8")
l1XDMProxy = common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294")
l1ERC721BridgeProxy = common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9")
default:
return fmt.Errorf("unknown network: %q", conf.Network)
}
// See Proxy.sol OWNER_KEY: https://eips.ethereum.org/EIPS/eip-1967#admin-address
ownerSlot := common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
// Address manager owner
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.Hash{}, conf.Owner.Hash())
// Ownable, first storage slot
headState.SetState(addressManager, common.Hash{}, conf.Owner.Hash())
// L1SB proxy owner
headState.SetState(common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"), common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"), conf.Owner.Hash())
headState.SetState(l1SBProxy, ownerSlot, conf.Owner.Hash())
// L1XDM owner
headState.SetState(common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"), common.Hash{31: 0x33}, conf.Owner.Hash())
// 0x33 = 51. L1CrossDomainMessenger is L1CrossDomainMessenger (0) Lib_AddressResolver (1) OwnableUpgradeable (1, but covered by gap) + ContextUpgradeable (special gap of 50) and then _owner
headState.SetState(l1XDMProxy, common.Hash{31: 0x33}, conf.Owner.Hash())
// L1 ERC721 bridge owner
headState.SetState(common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"), common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"), conf.Owner.Hash())
headState.SetState(l1ERC721BridgeProxy, ownerSlot, conf.Owner.Hash())
// Legacy sequencer/proposer addresses
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.HexToHash("0x2e0dfce60e9e27f035ce28f63c1bdd77cff6b13d8909da4d81d623ff9123fbdc"), conf.Sequencer.Hash())
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.HexToHash("0x9776dbdebd0d5eedaea450b21da9901ecd5254e5136a3a9b7b0ecd532734d5b5"), conf.Proposer.Hash())
// See AddressManager.sol "addresses" mapping(bytes32 => address), at slot position 1
addressesSlot := common.BigToHash(big.NewInt(1))
headState.SetState(addressManager, crypto.Keccak256Hash(crypto.Keccak256([]byte("OVM_Sequencer")), addressesSlot.Bytes()), conf.Sequencer.Hash())
headState.SetState(addressManager, crypto.Keccak256Hash(crypto.Keccak256([]byte("OVM_Proposer")), addressesSlot.Bytes()), conf.Proposer.Hash())
// Fund sequencer and proposer with 100 ETH
headState.SetBalance(conf.Sequencer, HundredETH)
headState.SetBalance(conf.Proposer, HundredETH)
......
......@@ -100,7 +100,6 @@ services:
OP_PROPOSER_RESUBMISSION_TIMEOUT: 30s
OP_PROPOSER_MNEMONIC: test test test test test test test test test test test junk
OP_PROPOSER_L2_OUTPUT_HD_PATH: "m/44'/60'/0'/0/1"
OP_PROPOSER_LOG_TERMINAL: "true"
OP_PROPOSER_L2OO_ADDRESS: "${L2OO_ADDRESS}"
OP_PROPOSER_PPROF_ENABLED: "true"
OP_PROPOSER_METRICS_ENABLED: "true"
......@@ -134,7 +133,6 @@ services:
OP_BATCHER_RESUBMISSION_TIMEOUT: 30s
OP_BATCHER_MNEMONIC: test test test test test test test test test test test junk
OP_BATCHER_SEQUENCER_HD_PATH: "m/44'/60'/0'/0/2"
OP_BATCHER_LOG_TERMINAL: "true"
OP_BATCHER_PPROF_ENABLED: "true"
OP_BATCHER_METRICS_ENABLED: "true"
OP_BATCHER_RPC_ENABLE_ADMIN: "true"
......
......@@ -16,9 +16,9 @@
# atst
atst is a typescript sdk and cli around the attestation station
atst is a typescript / javascript sdk and cli around AttestationStation
### Visit [Docs](https://community.optimism.io/docs/governance/attestation-station/) for general documentation on the attestation station!
**Visit [Docs](https://community.optimism.io/docs/governance/attestation-station/) for general documentation on AttestationStation.**
## Getting started
......@@ -28,42 +28,50 @@ Install
npm install @eth-optimism/atst wagmi @wagmi/core ethers@5.7.0
```
## atst typescript sdk
## atst typescript/javascript sdk
The typescript sdk provides a clean [wagmi](https://wagmi.sh/) based interface for reading and writing to the attestation station
The typescript sdk provides a clean [wagmi](https://wagmi.sh/) based interface for reading and writing to AttestationStation.
### See [sdk docs](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/sdk.md) for usage instructions
**See [sdk docs](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/sdk.md) for usage instructions.**
## atst cli
The cli provides a convenient cli for interacting with the attestation station contract
The cli provides a convenient cli for interacting with the AttestationStation contract
![preview](./assets/preview.gif)
## React API
**See [cli docs](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/cli.md) for usage instructions.**
For react hooks we recomend using the [wagmi cli](https://wagmi.sh/cli/getting-started) with the [etherscan plugin](https://wagmi.sh/cli/plugins/etherscan) and [react plugin](https://wagmi.sh/cli/plugins/react) to automatically generate react hooks around the attestation station.
## React API
Use `createKey` and `createValue` to convert your raw keys and values into bytes that can be used in the attestation station contract calls
For react hooks we recomend using the [wagmi cli](https://wagmi.sh/cli/getting-started) with the [etherscan plugin](https://wagmi.sh/cli/plugins/etherscan) and [react plugin](https://wagmi.sh/cli/plugins/react) to automatically generate react hooks around AttestationStation.
Use `parseString`, `parseBool`, `parseAddress` and `parseNumber` to convert values returned by attestation station to their correct data type.
Use `createKey` and `createValue` to convert your raw keys and values into bytes that can be used in AttestationStation contract calls
For convenience we also export the hooks here.
Use `parseString`, `parseBool`, `parseAddress` and `parseNumber` to convert values returned by AttestationStation to their correct data type.
`useAttestationStationAttestation` - Reads attestations with useContractRead
For convenience we also [export the hooks here](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/src/index.ts):
- `useAttestationStationAttestation` - Reads attestations with useContractRead
- `useAttestationStationVersion` - Reads attestation version
- `useAttestationStationAttest` - Wraps useContractWrite with AttestationStation abi calling attest
- `usePrepareAttestationStationAttest` - Wraps usePrepare with AttestationStation abi calling attest
- `useAttestationStationAttestationCreatedEvent` - Wraps useContractEvents for Created events
`useAttestationStationVersion` - Reads attestation version
Also some more hooks exported by the cli but these are likely the only ones you need.
`useAttestationStationAttest` - Wraps useContractWrite with attestation station abi calling attest
## Contributing
`usePrepareAttestationStationAttest` - Wraps usePrepare with attestation station abi calling attest
Please see our [contributing.md](https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md). No contribution is too small.
`useAttestationStationAttestationCreatedEvent` - Wraps useContractEvents for Created events
Having your contribution denied feels bad.
Please consider [opening an issue](https://github.com/ethereum-optimism/optimism/issues) before adding any new features or apis.
Also some more hooks exported by the cli but these are likely the only ones you need.
## Contributing
## Getting help
Please see our [contributing.md](docs/contributing.md). No contribution is too small.
If you have any problems, these resources could help you:
Having your contribution denied feels bad. Please consider opening an issue before adding any new features or apis
- [sdk documentation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/sdk.md)
- [cli documentation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/cli.md)
- [Optimism Discord](https://discord-gateway.optimism.io/)
- [Telegram group](https://t.me/+zwpJ8Ohqgl8yNjNh)
......@@ -14,10 +14,10 @@ npm install @eth-optimism/atst --global
npx atst <command> [options]
```
## Commands
### Commands
read read an attestation
write write an attestation
- `read` read an attestation
- `write` write an attestation
For more info, run any command with the `--help` flag:
......@@ -26,55 +26,46 @@ npx atst read --help
npx atst write --help
```
## Options:
### General options
-h, --help Display this message
-v, --version Display version number
- `-h`, `--help` Display help message
- `-v`, `--version` Display version number
## Usage:
```bash
npx atst <command> [options]
```
Commands:
read read an attestation
write write an attestation
For more info, run any command with the `--help` flag:
```bash
npx atst read --help
npx atst write --help
```
### Read
`--creator <string> Address of the creator of the attestation`
`--about <string> Address of the subject of the attestation`
`--key <string> Key of the attestation either as string or hex number`
`--data-type [string] Zod validator for the DataType type string | bytes | number | bool | address (default: string)`
`--rpc-url [url] Rpc url to use (default: https://mainnet.optimism.io)`
`--contract [address] Contract address to read from (default: 0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)`
`-h, --help Display this message`
- `--creator <address>` Address of the creator of the attestation
- `--about <address>` Address of the subject of the attestation
- `--key <string>` Key of the attestation either as string or hex number
- `[--data-type <string>]` The DataType type `string` | `bytes` | `number` | `bool` | `address` (default: `string`)
- `[--rpc-url <url>]` Rpc url to use (default: `https://mainnet.optimism.io`)
- `[--contract <address>]` Contract address to read from (default: `0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77`)
- `-h`, `--help` Display help message
Example:
```bash
npx atst read --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --creator 0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3
npx atst read --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 \
--creator 0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3
```
### write
### Write
- `--private-key <string>` Private key of the creator of the attestation
- `[--data-type <string>]` The DataType type `string` | `bytes` | `number` | `bool` | `address` (default: `string`)
- `--about <address>` Address of the subject of the attestation
- `--key <address>` Key of the attestation either as string or hex number
- `--value <string>` undefined
- `[--rpc-url <url>]` Rpc url to use (default: `https://mainnet.optimism.io`)
- `[--contract <address>]` Contract address to read from (default: 0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
- `-h`, `--help` Display this message
`--private-key <string> Address of the creator of the attestation`
`--data-type [string] Zod validator for the DataType type string | bytes | number | bool | address (default: string)`
`--about <string> Address of the subject of the attestation`
`--key <string> Key of the attestation either as string or hex number`
`--value <string> undefined`
`--rpc-url [url] Rpc url to use (default: https://mainnet.optimism.io)`
`--contract [address] Contract address to read from (default: 0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77) -h, --help Display this message`
Example:
```bash
atst write --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --value "my attestation" --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8545
atst/0.0.0
npx atst write --key "optimist.base-uri" \
--about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 \
--value "my attestation" \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--rpc-url http://goerli.optimism.io
```
This diff is collapsed.
......@@ -2,9 +2,9 @@
"name": "@eth-optimism/atst",
"version": "0.1.0",
"type": "module",
"main": "dist/index.js",
"main": "dist/index.cjs",
"types": "src/index.ts",
"module": "dist/index.cjs",
"module": "dist/index.js",
"license": "MIT",
"exports": {
".": {
......@@ -59,5 +59,15 @@
"@wagmi/core": "^0.9.2",
"@wagmi/cli": "~0.1.5",
"wagmi": "~0.11.0"
}
},
"keywords": [
"react",
"hooks",
"eth",
"ethereum",
"dapps",
"web3",
"optimism",
"attestation"
]
}
This diff is collapsed.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ILegacyMintableERC20 } from "../universal/OptimismMintableERC20.sol";
/**
* @title LegacyMintableERC20
* @notice The legacy implementation of the OptimismMintableERC20. This
* contract is deprecated and should no longer be used.
*/
contract LegacyMintableERC20 is ILegacyMintableERC20, ERC20 {
/**
* @notice Emitted when the token is minted by the bridge.
*/
event Mint(address indexed _account, uint256 _amount);
/**
* @notice Emitted when a token is burned by the bridge.
*/
event Burn(address indexed _account, uint256 _amount);
/**
* @notice The token on the remote domain.
*/
address public l1Token;
/**
* @notice The local bridge.
*/
address public l2Bridge;
/**
* @param _l2Bridge Address of the L2 standard bridge.
* @param _l1Token Address of the corresponding L1 token.
* @param _name ERC20 name.
* @param _symbol ERC20 symbol.
*/
constructor(
address _l2Bridge,
address _l1Token,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) {
l1Token = _l1Token;
l2Bridge = _l2Bridge;
}
/**
* @notice Modifier that requires the contract was called by the bridge.
*/
modifier onlyL2Bridge() {
require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");
_;
}
/**
* @notice EIP165 implementation.
*/
function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {
bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165
bytes4 secondSupportedInterface = ILegacyMintableERC20.l1Token.selector ^
ILegacyMintableERC20.mint.selector ^
ILegacyMintableERC20.burn.selector;
return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;
}
/**
* @notice Only the bridge can mint tokens.
* @param _to The account receiving tokens.
* @param _amount The amount of tokens to receive.
*/
function mint(address _to, uint256 _amount) public virtual onlyL2Bridge {
_mint(_to, _amount);
emit Mint(_to, _amount);
}
/**
* @notice Only the bridge can burn tokens.
* @param _from The account having tokens burnt.
* @param _amount The amount of tokens being burnt.
*/
function burn(address _from, uint256 _amount) public virtual onlyL2Bridge {
_burn(_from, _amount);
emit Burn(_from, _amount);
}
}
......@@ -8,27 +8,22 @@ import "./CommonTest.t.sol";
import { CrossDomainMessenger } from "../universal/CrossDomainMessenger.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
uint128 constant INITIAL_BASE_FEE = 1_000_000_000;
// Free function for setting the prevBaseFee param in the OptimismPortal.
function setPrevBaseFee(
Vm _vm,
address _op,
uint128 _prevBaseFee
) {
_vm.store(
address(_op),
bytes32(uint256(1)),
bytes32(
abi.encode(
ResourceMetering.ResourceParams({
prevBaseFee: _prevBaseFee,
prevBoughtGas: 0,
prevBlockNum: uint64(block.number)
})
)
)
);
_vm.store(address(_op), bytes32(uint256(1)), bytes32((block.number << 192) | _prevBaseFee));
}
contract SetPrevBaseFee_Test is Portal_Initializer {
function test_setPrevBaseFee_succeeds() external {
setPrevBaseFee(vm, address(op), 100 gwei);
(uint128 prevBaseFee, , uint64 prevBlockNum) = op.params();
assertEq(uint256(prevBaseFee), 100 gwei);
assertEq(uint256(prevBlockNum), block.number);
}
}
// Tests for obtaining pure gas cost estimates for commonly used functions.
......@@ -37,6 +32,8 @@ function setPrevBaseFee(
// In order to achieve this we make no assertions, and handle everything else in the setUp()
// function.
contract GasBenchMark_OptimismPortal is Portal_Initializer {
uint128 INITIAL_BASE_FEE;
// Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx;
......@@ -76,7 +73,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
}
// Get the system into a nice ready-to-use state.
function setUp() public override {
function setUp() public virtual override {
// Configure the oracle to return the output root we've prepared.
vm.warp(oracle.computeL2Timestamp(_proposedBlockNumber) + 1);
vm.prank(oracle.PROPOSER());
......@@ -88,6 +85,9 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
oracle.FINALIZATION_PERIOD_SECONDS() +
1
);
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
// Fund the portal so that we can withdraw ETH.
vm.deal(address(op), 0xFFFFFFFF);
}
......@@ -124,45 +124,83 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
}
contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
uint128 INITIAL_BASE_FEE;
function setUp() public virtual override {
super.setUp();
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
}
function test_sendMessage_benchmark_0() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
// The amount of data typically sent during a bridge deposit.
bytes
memory data = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
vm.resumeGasMetering();
L1Messenger.sendMessage(bob, data, uint32(100));
}
function test_sendMessage_benchmark_1() external {
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), 10 gwei);
// The amount of data typically sent during a bridge deposit.
bytes
memory data = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
vm.resumeGasMetering();
L1Messenger.sendMessage(bob, data, uint32(100));
}
}
contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
uint128 INITIAL_BASE_FEE;
function setUp() public virtual override {
super.setUp();
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
deal(address(L1Token), alice, 100000, true);
vm.startPrank(alice, alice);
L1Token.approve(address(L1Bridge), type(uint256).max);
}
function test_depositETH_benchmark_0() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
vm.resumeGasMetering();
L1Bridge.depositETH{ value: 500 }(50000, hex"");
}
function test_depositETH_benchmark_1() external {
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), 10 gwei);
vm.resumeGasMetering();
L1Bridge.depositETH{ value: 500 }(50000, hex"");
}
function test_depositERC20_benchmark_0() external {
L1Bridge.depositETH{ value: 500 }(50000, hex"");
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
vm.resumeGasMetering();
L1Bridge.bridgeERC20({
_localToken: address(L1Token),
_remoteToken: address(L2Token),
_amount: 100,
_minGasLimit: 100_000,
_extraData: hex""
});
}
function test_depositERC20_benchmark_1() external {
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
L1Bridge.depositETH{ value: 500 }(50000, hex"");
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), 10 gwei);
vm.resumeGasMetering();
L1Bridge.bridgeERC20({
_localToken: address(L1Token),
_remoteToken: address(L2Token),
_amount: 100,
_minGasLimit: 100_000,
_extraData: hex""
});
}
}
......
......@@ -27,6 +27,7 @@ import { AddressManager } from "../legacy/AddressManager.sol";
import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol";
contract CommonTest is Test {
address alice = address(128);
......@@ -155,8 +156,8 @@ contract L2OutputOracle_Initializer is CommonTest {
contract Portal_Initializer is L2OutputOracle_Initializer {
// Test target
OptimismPortal opImpl;
OptimismPortal op;
OptimismPortal internal opImpl;
OptimismPortal internal op;
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
event WithdrawalProven(
......@@ -176,14 +177,14 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
abi.encodeWithSelector(OptimismPortal.initialize.selector, false)
);
op = OptimismPortal(payable(address(proxy)));
vm.label(address(op), "OptimismPortal");
}
}
contract Messenger_Initializer is L2OutputOracle_Initializer {
OptimismPortal op;
AddressManager addressManager;
L1CrossDomainMessenger L1Messenger;
L2CrossDomainMessenger L2Messenger =
contract Messenger_Initializer is Portal_Initializer {
AddressManager internal addressManager;
L1CrossDomainMessenger internal L1Messenger;
L2CrossDomainMessenger internal L2Messenger =
L2CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER);
event SentMessage(
......@@ -219,17 +220,11 @@ contract Messenger_Initializer is L2OutputOracle_Initializer {
bytes data
);
event WithdrawalFinalized(bytes32 indexed, bool success);
event WhatHappened(bool success, bytes returndata);
function setUp() public virtual override {
super.setUp();
// Deploy the OptimismPortal
op = new OptimismPortal({ _l2Oracle: oracle, _guardian: guardian, _paused: false });
vm.label(address(op), "OptimismPortal");
// Deploy the address manager
vm.prank(multisig);
addressManager = new AddressManager();
......@@ -276,6 +271,7 @@ contract Bridge_Initializer is Messenger_Initializer {
ERC20 L1Token;
ERC20 BadL1Token;
OptimismMintableERC20 L2Token;
LegacyMintableERC20 LegacyL2Token;
ERC20 NativeL2Token;
ERC20 BadL2Token;
OptimismMintableERC20 RemoteL1Token;
......@@ -398,6 +394,14 @@ contract Bridge_Initializer is Messenger_Initializer {
L1Token = new ERC20("Native L1 Token", "L1T");
LegacyL2Token = new LegacyMintableERC20({
_l2Bridge: address(L2Bridge),
_l1Token: address(L1Token),
_name: string.concat("LegacyL2-", L1Token.name()),
_symbol: string.concat("LegacyL2-", L1Token.symbol())
});
vm.label(address(LegacyL2Token), "LegacyMintableERC20");
// Deploy the L2 ERC20 now
L2Token = OptimismMintableERC20(
L2TokenFactory.createStandardL2Token(
......
......@@ -155,15 +155,15 @@ contract L2StandardBridge_Test is Bridge_Initializer {
contract PreBridgeERC20 is Bridge_Initializer {
// withdraw and BridgeERC20 should behave the same when transferring ERC20 tokens
// so they should share the same setup and expectEmit calls
function _preBridgeERC20(bool isLegacy) internal {
function _preBridgeERC20(bool _isLegacy, address _l2Token) internal {
// Alice has 100 L2Token
deal(address(L2Token), alice, 100, true);
assertEq(L2Token.balanceOf(alice), 100);
deal(_l2Token, alice, 100, true);
assertEq(ERC20(_l2Token).balanceOf(alice), 100);
uint256 nonce = L2Messenger.messageNonce();
bytes memory message = abi.encodeWithSelector(
StandardBridge.finalizeBridgeERC20.selector,
address(L1Token),
address(L2Token),
_l2Token,
alice,
alice,
100,
......@@ -190,23 +190,17 @@ contract PreBridgeERC20 is Bridge_Initializer {
})
);
if (isLegacy) {
if (_isLegacy) {
vm.expectCall(
address(L2Bridge),
abi.encodeWithSelector(
L2Bridge.withdraw.selector,
address(L2Token),
100,
1000,
hex""
)
abi.encodeWithSelector(L2Bridge.withdraw.selector, _l2Token, 100, 1000, hex"")
);
} else {
vm.expectCall(
address(L2Bridge),
abi.encodeWithSelector(
L2Bridge.bridgeERC20.selector,
address(L2Token),
_l2Token,
address(L1Token),
100,
1000,
......@@ -237,15 +231,15 @@ contract PreBridgeERC20 is Bridge_Initializer {
// The L2Bridge should burn the tokens
vm.expectCall(
address(L2Token),
_l2Token,
abi.encodeWithSelector(OptimismMintableERC20.burn.selector, alice, 100)
);
vm.expectEmit(true, true, true, true);
emit WithdrawalInitiated(address(L1Token), address(L2Token), alice, alice, 100, hex"");
emit WithdrawalInitiated(address(L1Token), _l2Token, alice, alice, 100, hex"");
vm.expectEmit(true, true, true, true);
emit ERC20BridgeInitiated(address(L2Token), address(L1Token), alice, alice, 100, hex"");
emit ERC20BridgeInitiated(_l2Token, address(L1Token), alice, alice, 100, hex"");
vm.expectEmit(true, true, true, true);
emit MessagePassed(
......@@ -276,7 +270,7 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 {
// - emits WithdrawalInitiated
// - calls Withdrawer.initiateWithdrawal
function test_withdraw_withdrawingERC20_succeeds() external {
_preBridgeERC20({ isLegacy: true });
_preBridgeERC20({ _isLegacy: true, _l2Token: address(L2Token) });
L2Bridge.withdraw(address(L2Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
......@@ -287,12 +281,26 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 {
// - emits WithdrawalInitiated
// - calls Withdrawer.initiateWithdrawal
function test_bridgeERC20_succeeds() external {
_preBridgeERC20({ isLegacy: false });
_preBridgeERC20({ _isLegacy: false, _l2Token: address(L2Token) });
L2Bridge.bridgeERC20(address(L2Token), address(L1Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
}
function test_withdrawLegacyERC20_succeeds() external {
_preBridgeERC20({ _isLegacy: true, _l2Token: address(LegacyL2Token) });
L2Bridge.withdraw(address(LegacyL2Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
}
function test_bridgeLegacyERC20_succeeds() external {
_preBridgeERC20({ _isLegacy: false, _l2Token: address(LegacyL2Token) });
L2Bridge.bridgeERC20(address(LegacyL2Token), address(L1Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
}
function test_withdraw_notEOA_reverts() external {
// This contract has 100 L2Token
deal(address(L2Token), address(this), 100, true);
......@@ -305,14 +313,14 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 {
contract PreBridgeERC20To is Bridge_Initializer {
// withdrawTo and BridgeERC20To should behave the same when transferring ERC20 tokens
// so they should share the same setup and expectEmit calls
function _preBridgeERC20To(bool isLegacy) internal {
deal(address(L2Token), alice, 100, true);
assertEq(L2Token.balanceOf(alice), 100);
function _preBridgeERC20To(bool _isLegacy, address _l2Token) internal {
deal(_l2Token, alice, 100, true);
assertEq(ERC20(L2Token).balanceOf(alice), 100);
uint256 nonce = L2Messenger.messageNonce();
bytes memory message = abi.encodeWithSelector(
StandardBridge.finalizeBridgeERC20.selector,
address(L1Token),
address(L2Token),
_l2Token,
alice,
bob,
100,
......@@ -340,10 +348,10 @@ contract PreBridgeERC20To is Bridge_Initializer {
);
vm.expectEmit(true, true, true, true, address(L2Bridge));
emit WithdrawalInitiated(address(L1Token), address(L2Token), alice, bob, 100, hex"");
emit WithdrawalInitiated(address(L1Token), _l2Token, alice, bob, 100, hex"");
vm.expectEmit(true, true, true, true, address(L2Bridge));
emit ERC20BridgeInitiated(address(L2Token), address(L1Token), alice, bob, 100, hex"");
emit ERC20BridgeInitiated(_l2Token, address(L1Token), alice, bob, 100, hex"");
vm.expectEmit(true, true, true, true, address(messagePasser));
emit MessagePassed(
......@@ -364,12 +372,12 @@ contract PreBridgeERC20To is Bridge_Initializer {
vm.expectEmit(true, true, true, true, address(L2Messenger));
emit SentMessageExtension1(address(L2Bridge), 0);
if (isLegacy) {
if (_isLegacy) {
vm.expectCall(
address(L2Bridge),
abi.encodeWithSelector(
L2Bridge.withdrawTo.selector,
address(L2Token),
_l2Token,
bob,
100,
1000,
......@@ -381,7 +389,7 @@ contract PreBridgeERC20To is Bridge_Initializer {
address(L2Bridge),
abi.encodeWithSelector(
L2Bridge.bridgeERC20To.selector,
address(L2Token),
_l2Token,
address(L1Token),
bob,
100,
......@@ -427,7 +435,7 @@ contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To {
// - emits WithdrawalInitiated w/ correct recipient
// - calls Withdrawer.initiateWithdrawal
function test_withdrawTo_withdrawingERC20_succeeds() external {
_preBridgeERC20To({ isLegacy: true });
_preBridgeERC20To({ _isLegacy: true, _l2Token: address(L2Token) });
L2Bridge.withdrawTo(address(L2Token), bob, 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
......@@ -438,7 +446,7 @@ contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To {
// - emits WithdrawalInitiated w/ correct recipient
// - calls Withdrawer.initiateWithdrawal
function test_bridgeERC20To_succeeds() external {
_preBridgeERC20To({ isLegacy: false });
_preBridgeERC20To({ _isLegacy: false, _l2Token: address(L2Token) });
L2Bridge.bridgeERC20To(address(L2Token), address(L1Token), bob, 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
}
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { StandardBridge } from "../universal/StandardBridge.sol";
import { CommonTest } from "./CommonTest.t.sol";
import {
OptimismMintableERC20,
ILegacyMintableERC20
} from "../universal/OptimismMintableERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @title StandardBridgeTester
* @notice Simple wrapper around the StandardBridge contract that exposes
* internal functions so they can be more easily tested directly.
*/
contract StandardBridgeTester is StandardBridge {
constructor(address payable _messenger, address payable _otherBridge)
StandardBridge(_messenger, _otherBridge)
{}
function isOptimismMintableERC20(address _token) external view returns (bool) {
return _isOptimismMintableERC20(_token);
}
function isCorrectTokenPair(address _mintableToken, address _otherToken)
external
view
returns (bool)
{
return _isCorrectTokenPair(_mintableToken, _otherToken);
}
receive() external payable override {}
}
/**
* @title LegacyMintable
* @notice Simple implementation of the legacy OptimismMintableERC20.
*/
contract LegacyMintable is ERC20, ILegacyMintableERC20 {
constructor(string memory _name, string memory _ticker) ERC20(_name, _ticker) {}
function l1Token() external pure returns (address) {
return address(0);
}
function mint(address _to, uint256 _amount) external pure {}
function burn(address _from, uint256 _amount) external pure {}
/**
* @notice Implements ERC165. This implementation should not be changed as
* it is how the actual legacy optimism mintable token does the
* check. Allows for testing against code that is has been deployed,
* assuming different compiler version is no problem.
*/
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {
bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165
bytes4 secondSupportedInterface = ILegacyMintableERC20.l1Token.selector ^
ILegacyMintableERC20.mint.selector ^
ILegacyMintableERC20.burn.selector;
return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;
}
}
/**
* @title StandardBridge_Stateless_Test
* @notice Tests internal functions that require no existing state or contract
* interactions with the messenger.
*/
contract StandardBridge_Stateless_Test is CommonTest {
StandardBridgeTester internal bridge;
OptimismMintableERC20 internal mintable;
ERC20 internal erc20;
LegacyMintable internal legacy;
function setUp() public override {
super.setUp();
bridge = new StandardBridgeTester({
_messenger: payable(address(0)),
_otherBridge: payable(address(0))
});
mintable = new OptimismMintableERC20({
_bridge: address(0),
_remoteToken: address(0),
_name: "Stonks",
_symbol: "STONK"
});
erc20 = new ERC20("Altcoin", "ALT");
legacy = new LegacyMintable("Legacy", "LEG");
}
/**
* @notice Test coverage for identifying OptimismMintableERC20 tokens.
* This function should return true for both modern and legacy
* OptimismMintableERC20 tokens and false for any accounts that
* do not implement the interface.
*/
function test_isOptimismMintableERC20_succeeds() external {
// Both the modern and legacy mintable tokens should return true
assertTrue(bridge.isOptimismMintableERC20(address(mintable)));
assertTrue(bridge.isOptimismMintableERC20(address(legacy)));
// A regular ERC20 should return false
assertFalse(bridge.isOptimismMintableERC20(address(erc20)));
// Non existent contracts should return false and not revert
assertEq(address(0x20).code.length, 0);
assertFalse(bridge.isOptimismMintableERC20(address(0x20)));
}
/**
* @notice Test coverage of isCorrectTokenPair under different types of
* tokens.
*/
function test_isCorrectTokenPair_succeeds() external {
// Modern + known to be correct remote token
assertTrue(bridge.isCorrectTokenPair(address(mintable), mintable.remoteToken()));
// Modern + known to be correct l1Token (legacy interface)
assertTrue(bridge.isCorrectTokenPair(address(mintable), mintable.l1Token()));
// Modern + known to be incorrect remote token
assertTrue(mintable.remoteToken() != address(0x20));
assertFalse(bridge.isCorrectTokenPair(address(mintable), address(0x20)));
// Legacy + known to be correct l1Token
assertTrue(bridge.isCorrectTokenPair(address(legacy), legacy.l1Token()));
// Legacy + known to be incorrect l1Token
assertTrue(legacy.l1Token() != address(0x20));
assertFalse(bridge.isCorrectTokenPair(address(legacy), address(0x20)));
// A token that doesn't support either modern or legacy interface
// will revert
vm.expectRevert();
bridge.isCorrectTokenPair(address(erc20), address(1));
}
}
......@@ -141,16 +141,6 @@ abstract contract CrossDomainMessenger is
*/
uint64 public constant MIN_GAS_CALLDATA_OVERHEAD = 16;
/**
* @notice Minimum amount of gas required to relay a message.
*/
uint256 internal constant RELAY_GAS_REQUIRED = 45_000;
/**
* @notice Amount of gas held in reserve to guarantee that relay execution completes.
*/
uint256 internal constant RELAY_GAS_BUFFER = RELAY_GAS_REQUIRED - 5000;
/**
* @notice Address of the paired CrossDomainMessenger contract on the other chain.
*/
......@@ -367,16 +357,11 @@ abstract contract CrossDomainMessenger is
"CrossDomainMessenger: message has already been relayed"
);
require(
gasleft() >= _minGasLimit + RELAY_GAS_REQUIRED,
"CrossDomainMessenger: insufficient gas to relay message"
);
xDomainMsgSender = _sender;
bool success = SafeCall.call(_target, gasleft() - RELAY_GAS_BUFFER, _value, _message);
bool success = SafeCall.callWithMinGas(_target, _minGasLimit, _value, _message);
xDomainMsgSender = Constants.DEFAULT_L2_SENDER;
if (success == true) {
if (success) {
successfulMessages[versionedHash] = true;
emit RelayedMessage(versionedHash);
} else {
......
......@@ -10,7 +10,7 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol
* OptimismMintableERC20.
*/
interface IOptimismMintableERC20 is IERC165 {
function remoteToken() external returns (address);
function remoteToken() external view returns (address);
function bridge() external returns (address);
......@@ -26,7 +26,7 @@ interface IOptimismMintableERC20 is IERC165 {
* on the OptimismMintableERC20 contract for backwards compatibility.
*/
interface ILegacyMintableERC20 is IERC165 {
function l1Token() external returns (address);
function l1Token() external view returns (address);
function mint(address _to, uint256 _amount) external;
......
......@@ -458,6 +458,8 @@ abstract contract StandardBridge {
/**
* @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20.
* Calls can be saved in the future by combining this logic with
* `_isOptimismMintableERC20`.
*
* @param _mintableToken OptimismMintableERC20 to check against.
* @param _otherToken Pair token to check.
......@@ -469,7 +471,13 @@ abstract contract StandardBridge {
view
returns (bool)
{
return _otherToken == OptimismMintableERC20(_mintableToken).l1Token();
if (
ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)
) {
return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token();
} else {
return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken();
}
}
/** @notice Emits the ETHBridgeInitiated event and if necessary the appropriate legacy event
......
......@@ -23,7 +23,6 @@ const deployFn: DeployFunction = async (hre) => {
SystemDictator,
ProxyAdmin,
AddressManager,
L1CrossDomainMessenger,
L1StandardBridgeProxy,
L1StandardBridgeProxyWithSigner,
L1ERC721BridgeProxy,
......@@ -43,11 +42,6 @@ const deployFn: DeployFunction = async (hre) => {
name: 'Lib_AddressManager',
signerOrProvider: deployer,
},
{
name: 'Proxy__OVM_L1CrossDomainMessenger',
iface: 'L1CrossDomainMessenger',
signerOrProvider: deployer,
},
{
name: 'Proxy__OVM_L1StandardBridge',
},
......@@ -118,41 +112,6 @@ const deployFn: DeployFunction = async (hre) => {
console.log(`AddressManager already owned by the SystemDictator`)
}
// Transfer ownership of the L1CrossDomainMessenger to SystemDictator.
if (
needsProxyTransfer &&
(await AddressManager.getAddress('OVM_L1CrossDomainMessenger')) !==
ethers.constants.AddressZero &&
(await L1CrossDomainMessenger.owner()) !== SystemDictator.address
) {
if (isLiveDeployer) {
console.log(`Setting L1CrossDomainMessenger owner to MSD`)
await L1CrossDomainMessenger.transferOwnership(SystemDictator.address)
} else {
const tx =
await L1CrossDomainMessenger.populateTransaction.transferOwnership(
SystemDictator.address
)
console.log(`Please transfer L1CrossDomainMessenger owner to MSD`)
console.log(`L1XDM address: ${L1CrossDomainMessenger.address}`)
console.log(`MSD address: ${SystemDictator.address}`)
console.log(`JSON:`)
console.log(jsonifyTransaction(tx))
}
// Wait for the ownership transfer to complete.
await awaitCondition(
async () => {
const owner = await L1CrossDomainMessenger.owner()
return owner === SystemDictator.address
},
30000,
1000
)
} else {
console.log(`L1CrossDomainMessenger already owned by MSD`)
}
// Transfer ownership of the L1StandardBridge (proxy) to SystemDictator.
if (
needsProxyTransfer &&
......
......@@ -124,7 +124,7 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
this.state.oo = {
contract: oo,
filter: oo.filters.OutputProposed(),
getTotalElements: async () => oo.latestOutputIndex(),
getTotalElements: async () => oo.nextOutputIndex(),
getEventIndex: (args) => args.l2OutputIndex,
}
} else {
......
......@@ -18,12 +18,10 @@ import {
sleep,
remove0x,
toHexString,
fromHexString,
toRpcHexString,
hashCrossDomainMessage,
encodeCrossDomainMessageV0,
encodeCrossDomainMessageV1,
L2OutputOracleParameters,
BedrockOutputData,
BedrockCrossChainMessageProof,
decodeVersionedNonce,
......@@ -67,6 +65,7 @@ import {
makeMerkleTreeProof,
makeStateTrieProof,
hashLowLevelMessage,
migratedWithdrawalGasLimit,
DEPOSIT_CONFIRMATION_BLOCKS,
CHAIN_BLOCK_TIMES,
} from './utils'
......@@ -352,12 +351,12 @@ export class CrossChainMessenger {
}
}
const minGasLimit = fromHexString(resolved.message).length * 16 + 200_000
const minGasLimit = migratedWithdrawalGasLimit(resolved.message)
return {
...resolved,
value,
minGasLimit: BigNumber.from(minGasLimit),
minGasLimit,
messageNonce: encodeVersionedNonce(
BigNumber.from(1),
resolved.messageNonce
......
import { hashWithdrawal } from '@eth-optimism/core-utils'
import { hashWithdrawal, calldataCost } from '@eth-optimism/core-utils'
import { BigNumber } from 'ethers'
import { LowLevelMessage } from '../interfaces'
......@@ -18,3 +19,16 @@ export const hashLowLevelMessage = (message: LowLevelMessage): string => {
message.message
)
}
/**
* Compute the min gas limit for a migrated withdrawal.
*/
export const migratedWithdrawalGasLimit = (data: string): BigNumber => {
// Compute the gas limit and cap at 25 million
const dataCost = calldataCost(data)
let minGasLimit = dataCost.add(200_000)
if (minGasLimit.gt(25_000_000)) {
minGasLimit = BigNumber.from(25_000_000)
}
return minGasLimit
}
import { BigNumber } from 'ethers'
import { expect } from '../setup'
import { migratedWithdrawalGasLimit } from '../../src/utils/message-utils'
describe('Message Utils', () => {
describe('migratedWithdrawalGasLimit', () => {
it('should have a max of 25 million', () => {
const data = '0x' + 'ff'.repeat(15_000_000)
const result = migratedWithdrawalGasLimit(data)
expect(result).to.eq(BigNumber.from(25_000_000))
})
it('should work for mixes of zeros and ones', () => {
const tests = [
{ input: '0x', result: BigNumber.from(200_000) },
{ input: '0xff', result: BigNumber.from(200_000 + 16) },
{ input: '0xff00', result: BigNumber.from(200_000 + 16 + 4) },
{ input: '0x00', result: BigNumber.from(200_000 + 4) },
{ input: '0x000000', result: BigNumber.from(200_000 + 4 + 4 + 4) },
]
for (const test of tests) {
const result = migratedWithdrawalGasLimit(test.input)
expect(result).to.eq(test.result)
}
})
})
})
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