Commit 659b43f9 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #4969 from ethereum-optimism/develop

Develop -> Master
parents 29c26522 aa675751
---
'@eth-optimism/ci-builder': patch
---
Bump foundry to edf15abd648bb96e2bcee342c1d72ec7d1066cd1
---
"@eth-optimism/indexer": patch
---
build(deps): bump golang.org/x/text from 0.3.7 to 0.3.8 in /indexer
---
'@eth-optimism/ci-builder': minor
'@eth-optimism/contracts-bedrock': patch
---
Update foundry
......@@ -26,11 +26,9 @@ jobs:
replica-healthcheck: ${{ steps.packages.outputs.replica-healthcheck }}
hardhat-node: ${{ steps.packages.outputs.hardhat-node }}
canary-docker-tag: ${{ steps.docker-image-name.outputs.canary-docker-tag }}
proxyd: ${{ steps.packages.outputs.proxyd }}
op-exporter: ${{ steps.packages.outputs.op-exporter }}
l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
indexer: ${{ steps.packages.outputs.indexer }}
endpoint-monitor: ${{ steps.packages.outputs.l2geth-exporter }}
steps:
......@@ -393,43 +391,6 @@ jobs:
push: true
tags: ethereumoptimism/replica-healthcheck:${{ needs.canary-publish.outputs.canary-docker-tag }}
proxyd:
name: Publish proxyd Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.proxyd != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./proxyd/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./proxyd/Dockerfile
push: true
tags: ethereumoptimism/proxyd:${{ needs.canary-publish.outputs.proxyd }}
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
op-exporter:
name: Publish op-exporter Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......@@ -530,43 +491,6 @@ jobs:
push: true
tags: ethereumoptimism/batch-submitter-service:${{ needs.canary-publish.outputs.batch-submitter-service }}
indexer:
name: Publish indexer Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.indexer != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./indexer/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./indexer/Dockerfile
push: true
tags: ethereumoptimism/indexer:${{ needs.canary-publish.outputs.indexer }}
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
endpoint-monitor:
name: Publish endpoint-monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......
......@@ -21,12 +21,10 @@ jobs:
balance-monitor: ${{ steps.packages.outputs.balance-monitor }}
gas-oracle: ${{ steps.packages.outputs.gas-oracle }}
replica-healthcheck: ${{ steps.packages.outputs.replica-healthcheck }}
proxyd: ${{ steps.packages.outputs.proxyd }}
hardhat-node: ${{ steps.packages.outputs.hardhat-node }}
op-exporter: ${{ steps.packages.outputs.op-exporter }}
l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
indexer: ${{ steps.packages.outputs.indexer }}
ci-builder: ${{ steps.packages.outputs.ci-builder }}
foundry: ${{ steps.packages.outputs.foundry }}
endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }}
......@@ -210,43 +208,6 @@ jobs:
push: true
tags: ethereumoptimism/foundry:${{ needs.release.outputs.foundry }},ethereumoptimism/foundry:latest
proxyd:
name: Publish proxyd Version ${{ needs.release.outputs.proxyd }}
needs: release
if: needs.release.outputs.proxyd != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./proxyd/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./proxyd/Dockerfile
push: true
tags: ethereumoptimism/proxyd:${{ needs.release.outputs.proxyd }},ethereumoptimism/proxyd:latest
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
l2geth-exporter:
name: Publish l2geth-exporter Version ${{ needs.release.outputs.l2geth-exporter}}
needs: release
......@@ -590,43 +551,6 @@ jobs:
push: true
tags: ethereumoptimism/batch-submitter-service:${{ needs.release.outputs.batch-submitter-service }},ethereumoptimism/batch-submitter-service:latest
indexer:
name: Publish Indexer Version ${{ needs.release.outputs.indexer }}
needs: release
if: needs.release.outputs.indexer != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./indexer/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Publish Indexer
uses: docker/build-push-action@v2
with:
context: .
file: ./indexer/Dockerfile
push: true
tags: ethereumoptimism/indexer:${{ needs.release.outputs.indexer }},ethereumoptimism/indexer:latest
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
endpoint-monitor:
name: Publish endpoint-monitor Version ${{ needs.release.outputs.endpoint-monitor}}
needs: release
......
......@@ -152,7 +152,7 @@ require (
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/text v0.3.8 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.1 // indirect
......
......@@ -1404,8 +1404,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
{
"name": "@eth-optimism/indexer",
"version": "0.7.0",
"private": true,
"license": "MIT"
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
......@@ -192,7 +192,7 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
}
// Finally we migrate the balances held inside the LegacyERC20ETH contract into the state trie.
// Note that we do NOT delete the balances from the LegacyERC20ETH contract.
// We also delete the balances from the LegacyERC20ETH contract.
log.Info("Starting to migrate ERC20 ETH")
err = ether.MigrateLegacyETH(db, addrs, int(config.L1ChainID), noCheck)
if err != nil {
......
......@@ -123,7 +123,8 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
if err != nil {
return nil, err
}
data, err = portalABI.Pack("initialize")
// Initialize the OptimismPortal without being paused
data, err = portalABI.Pack("initialize", false)
if err != nil {
return nil, fmt.Errorf("cannot abi encode initialize for OptimismPortal: %w", err)
}
......@@ -290,9 +291,15 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
},
},
{
// The implementation of the OptimismPortal is deployed
// as being paused to prevent invalid usage of the network
// as only the proxy should be used
Name: "OptimismPortal",
Args: []interface{}{
predeploys.DevL2OutputOracleAddr,
config.FinalSystemOwner,
uint642Big(config.FinalizationPeriodSeconds),
true, // _paused
},
},
{
......@@ -354,8 +361,10 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
_, tx, _, err = bindings.DeployOptimismPortal(
opts,
backend,
predeploys.DevL2OutputOracleAddr,
deployment.Args[0].(*big.Int),
deployment.Args[0].(common.Address),
deployment.Args[2].(*big.Int),
deployment.Args[1].(common.Address),
deployment.Args[3].(bool),
)
case "L1CrossDomainMessenger":
_, tx, _, err = bindings.DeployL1CrossDomainMessenger(
......
package op_e2e
import (
"context"
"errors"
"fmt"
"math/big"
"reflect"
"testing"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
gn "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
var (
// ErrForkChoiceUpdatedNotValid is returned when a forkChoiceUpdated returns a status other than Valid
ErrForkChoiceUpdatedNotValid = errors.New("forkChoiceUpdated status was not valid")
// ErrNewPayloadNotValid is returned when a newPayload call returns a status other than Valid, indicating the new block is invalid
ErrNewPayloadNotValid = errors.New("newPayload status was not valid")
)
// OpGeth is an actor that functions as a l2 op-geth node
// It provides useful functions for advancing and querying the chain
type OpGeth struct {
node *gn.Node
l2Engine *sources.EngineClient
L2Client *ethclient.Client
SystemConfig eth.SystemConfig
L1ChainConfig *params.ChainConfig
L2ChainConfig *params.ChainConfig
L1Head eth.BlockInfo
L2Head *eth.ExecutionPayload
sequenceNum uint64
}
func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, error) {
logger := testlog.Logger(t, log.LvlCrit)
l1Genesis, err := genesis.BuildL1DeveloperGenesis(cfg.DeployConfig)
require.Nil(t, err)
l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(cfg.DeployConfig, l1Block)
require.Nil(t, err)
l2GenesisBlock := l2Genesis.ToBlock()
rollupGenesis := rollup.Genesis{
L1: eth.BlockID{
Hash: l1Block.Hash(),
Number: l1Block.NumberU64(),
},
L2: eth.BlockID{
Hash: l2GenesisBlock.Hash(),
Number: l2GenesisBlock.NumberU64(),
},
L2Time: l2GenesisBlock.Time(),
SystemConfig: e2eutils.SystemConfigFromDeployConfig(cfg.DeployConfig),
}
node, _, err := initL2Geth("l2", big.NewInt(int64(cfg.DeployConfig.L2ChainID)), l2Genesis, cfg.JWTFilePath)
require.Nil(t, err)
require.Nil(t, node.Start())
auth := rpc.WithHTTPAuth(gn.NewJWTAuth(cfg.JWTSecret))
l2Node, err := client.NewRPC(ctx, logger, node.WSAuthEndpoint(), auth)
require.Nil(t, err)
// Finally create the engine client
l2Engine, err := sources.NewEngineClient(
l2Node,
logger,
nil,
sources.EngineClientDefaultConfig(&rollup.Config{Genesis: rollupGenesis}),
)
require.Nil(t, err)
l2Client, err := ethclient.Dial(node.HTTPEndpoint())
require.Nil(t, err)
genesisPayload, err := eth.BlockAsPayload(l2GenesisBlock)
require.Nil(t, err)
return &OpGeth{
node: node,
L2Client: l2Client,
l2Engine: l2Engine,
SystemConfig: rollupGenesis.SystemConfig,
L1ChainConfig: l1Genesis.Config,
L2ChainConfig: l2Genesis.Config,
L1Head: l1Block,
L2Head: genesisPayload,
}, nil
}
func (d *OpGeth) Close() {
_ = d.node.Close()
d.l2Engine.Close()
d.L2Client.Close()
}
// AddL2Block Appends a new L2 block to the current chain including the specified transactions
// The L1Info transaction is automatically prepended to the created block
func (d *OpGeth) AddL2Block(ctx context.Context, txs ...*types.Transaction) (*eth.ExecutionPayload, error) {
attrs, err := d.CreatePayloadAttributes(txs...)
if err != nil {
return nil, err
}
res, err := d.StartBlockBuilding(ctx, attrs)
if err != nil {
return nil, err
}
payload, err := d.l2Engine.GetPayload(ctx, *res.PayloadID)
if err != nil {
return nil, err
}
if !reflect.DeepEqual(payload.Transactions, attrs.Transactions) {
return nil, errors.New("required transactions were not included")
}
status, err := d.l2Engine.NewPayload(ctx, payload)
if err != nil {
return nil, err
}
if status.Status != eth.ExecutionValid {
return nil, fmt.Errorf("%w: %s", ErrNewPayloadNotValid, status.Status)
}
fc := eth.ForkchoiceState{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: payload.BlockHash,
}
res, err = d.l2Engine.ForkchoiceUpdate(ctx, &fc, nil)
if err != nil {
return nil, err
}
if res.PayloadStatus.Status != eth.ExecutionValid {
return nil, fmt.Errorf("%w: %s", ErrForkChoiceUpdatedNotValid, res.PayloadStatus.Status)
}
d.L2Head = payload
d.sequenceNum = d.sequenceNum + 1
return payload, nil
}
// StartBlockBuilding begins block building for the specified PayloadAttributes by sending a engine_forkChoiceUpdated call.
// The current L2Head is used as the parent of the new block.
// ErrForkChoiceUpdatedNotValid is returned if the forkChoiceUpdate call returns a status other than valid.
func (d *OpGeth) StartBlockBuilding(ctx context.Context, attrs *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
fc := eth.ForkchoiceState{
HeadBlockHash: d.L2Head.BlockHash,
SafeBlockHash: d.L2Head.BlockHash,
}
res, err := d.l2Engine.ForkchoiceUpdate(ctx, &fc, attrs)
if err != nil {
return nil, err
}
if res.PayloadStatus.Status != eth.ExecutionValid {
return nil, fmt.Errorf("%w: %s", ErrForkChoiceUpdatedNotValid, res.PayloadStatus.Status)
}
if res.PayloadID == nil {
return nil, errors.New("forkChoiceUpdated returned nil PayloadID")
}
return res, nil
}
// CreatePayloadAttributes creates a valid PayloadAttributes containing a L1Info deposit transaction followed by the supplied transactions.
func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.PayloadAttributes, error) {
timestamp := d.L2Head.Timestamp + 2
l1Info, err := derive.L1InfoDepositBytes(d.sequenceNum, d.L1Head, d.SystemConfig, false)
if err != nil {
return nil, err
}
var txBytes []hexutil.Bytes
txBytes = append(txBytes, l1Info)
for _, tx := range txs {
bin, err := tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("tx marshalling failed: %w", err)
}
txBytes = append(txBytes, bin)
}
attrs := eth.PayloadAttributes{
Timestamp: timestamp,
Transactions: txBytes,
NoTxPool: true,
GasLimit: (*eth.Uint64Quantity)(&d.SystemConfig.GasLimit),
}
return &attrs, nil
}
......@@ -6,91 +6,29 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
gn "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
// TestMissingGasLimit tests that op-geth cannot build a block without gas limit while optimism is active in the chain config.
func TestMissingGasLimit(t *testing.T) {
// Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis.
log := testlog.Logger(t, log.LvlCrit)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
l1Genesis, err := genesis.BuildL1DeveloperGenesis(cfg.DeployConfig)
require.Nil(t, err)
l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(cfg.DeployConfig, l1Block)
require.Nil(t, err)
l2GenesisBlock := l2Genesis.ToBlock()
rollupGenesis := rollup.Genesis{
L1: eth.BlockID{
Hash: l1Block.Hash(),
Number: l1Block.NumberU64(),
},
L2: eth.BlockID{
Hash: l2GenesisBlock.Hash(),
Number: l2GenesisBlock.NumberU64(),
},
L2Time: l2GenesisBlock.Time(),
SystemConfig: e2eutils.SystemConfigFromDeployConfig(cfg.DeployConfig),
}
node, _, err := initL2Geth("l2", big.NewInt(int64(cfg.DeployConfig.L2ChainID)), l2Genesis, writeDefaultJWT(t))
require.Nil(t, err)
require.Nil(t, node.Start())
defer node.Close()
auth := rpc.WithHTTPAuth(gn.NewJWTAuth(testingJWTSecret))
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
l2Node, err := client.NewRPC(ctx, log, node.WSAuthEndpoint(), auth)
require.Nil(t, err)
// Finally create the engine client
client, err := sources.NewEngineClient(
l2Node,
log,
nil,
sources.EngineClientDefaultConfig(&rollup.Config{Genesis: rollupGenesis}),
)
require.Nil(t, err)
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
attrs := eth.PayloadAttributes{
Timestamp: hexutil.Uint64(l2GenesisBlock.Time() + 2),
Transactions: []hexutil.Bytes{},
NoTxPool: true,
GasLimit: nil, // no gas limit
}
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
attrs, err := opGeth.CreatePayloadAttributes()
require.NoError(t, err)
// Remove the GasLimit from the otherwise valid attributes
attrs.GasLimit = nil
fc := eth.ForkchoiceState{
HeadBlockHash: l2GenesisBlock.Hash(),
SafeBlockHash: l2GenesisBlock.Hash(),
}
res, err := client.ForkchoiceUpdate(ctx, &fc, &attrs)
res, err := opGeth.StartBlockBuilding(ctx, attrs)
require.ErrorIs(t, err, eth.InputError{})
require.Equal(t, eth.InvalidPayloadAttributes, err.(eth.InputError).Code)
require.Nil(t, res)
......@@ -99,109 +37,36 @@ func TestMissingGasLimit(t *testing.T) {
// TestInvalidDepositInFCU runs an invalid deposit through a FCU/GetPayload/NewPayload/FCU set of calls.
// This tests that deposits must always allow the block to be built even if they are invalid.
func TestInvalidDepositInFCU(t *testing.T) {
// Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis.
log := testlog.Logger(t, log.LvlCrit)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
l1Genesis, err := genesis.BuildL1DeveloperGenesis(cfg.DeployConfig)
require.Nil(t, err)
l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(cfg.DeployConfig, l1Block)
require.Nil(t, err)
l2GenesisBlock := l2Genesis.ToBlock()
rollupGenesis := rollup.Genesis{
L1: eth.BlockID{
Hash: l1Block.Hash(),
Number: l1Block.NumberU64(),
},
L2: eth.BlockID{
Hash: l2GenesisBlock.Hash(),
Number: l2GenesisBlock.NumberU64(),
},
L2Time: l2GenesisBlock.Time(),
SystemConfig: e2eutils.SystemConfigFromDeployConfig(cfg.DeployConfig),
}
node, _, err := initL2Geth("l2", big.NewInt(int64(cfg.DeployConfig.L2ChainID)), l2Genesis, writeDefaultJWT(t))
require.Nil(t, err)
require.Nil(t, node.Start())
defer node.Close()
auth := rpc.WithHTTPAuth(gn.NewJWTAuth(testingJWTSecret))
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
l2Node, err := client.NewRPC(ctx, log, node.WSAuthEndpoint(), auth)
require.Nil(t, err)
// Finally create the engine client
client, err := sources.NewEngineClient(
l2Node,
log,
nil,
sources.EngineClientDefaultConfig(&rollup.Config{Genesis: rollupGenesis}),
)
require.Nil(t, err)
// Create the test data (L1 Info Tx and then always failing deposit)
l1Info, err := derive.L1InfoDepositBytes(1, l1Block, rollupGenesis.SystemConfig)
require.Nil(t, err)
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
// Create a deposit from alice that will always fail (not enough funds)
fromAddr := cfg.Secrets.Addresses().Alice
l2Client, err := ethclient.Dial(node.HTTPEndpoint())
require.Nil(t, err)
balance, err := l2Client.BalanceAt(ctx, fromAddr, nil)
balance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.Nil(t, err)
require.Equal(t, 0, balance.Cmp(common.Big0))
badDepositTx := types.NewTx(&types.DepositTx{
// TODO: Source Hash
SourceHash: opGeth.L1Head.Hash(),
From: fromAddr,
To: &fromAddr, // send it to ourselves
Value: big.NewInt(params.Ether),
Gas: 25000,
IsSystemTransaction: false,
})
badDeposit, err := badDepositTx.MarshalBinary()
require.Nil(t, err)
attrs := eth.PayloadAttributes{
Timestamp: hexutil.Uint64(l2GenesisBlock.Time() + 2),
Transactions: []hexutil.Bytes{l1Info, badDeposit},
NoTxPool: true,
GasLimit: (*eth.Uint64Quantity)(&rollupGenesis.SystemConfig.GasLimit),
}
// Go through the flow of FCU, GetPayload, NewPayload, FCU
// We are inserting a block with an invalid deposit.
// The invalid deposit should still remain in the block.
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_, err = opGeth.AddL2Block(ctx, badDepositTx)
require.NoError(t, err)
fc := eth.ForkchoiceState{
HeadBlockHash: l2GenesisBlock.Hash(),
SafeBlockHash: l2GenesisBlock.Hash(),
}
res, err := client.ForkchoiceUpdate(ctx, &fc, &attrs)
// Deposit tx was included, but Alice still shouldn't have any ETH
balance, err = opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.Nil(t, err)
require.Equal(t, eth.ExecutionValid, res.PayloadStatus.Status)
require.NotNil(t, res.PayloadID)
payload, err := client.GetPayload(ctx, *res.PayloadID)
require.Nil(t, err)
require.NotNil(t, payload)
require.Equal(t, payload.Transactions, attrs.Transactions) // Ensure we don't drop the transactions
status, err := client.NewPayload(ctx, payload)
require.Nil(t, err)
require.Equal(t, eth.ExecutionValid, status.Status)
fc.HeadBlockHash = payload.BlockHash
res, err = client.ForkchoiceUpdate(ctx, &fc, nil)
require.Nil(t, err)
require.Equal(t, eth.ExecutionValid, res.PayloadStatus.Status)
require.Equal(t, 0, balance.Cmp(common.Big0))
}
......@@ -34,9 +34,9 @@ require (
github.com/prometheus/procfs v0.7.3 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/text v0.3.8 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.27.1 // indirect
......
......@@ -635,8 +635,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
......@@ -650,8 +650,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
......@@ -73,6 +73,14 @@ var NetworksByName = map[string]rollup.Config{
"goerli": Goerli,
}
var L2ChainIDToNetworkName = func() map[string]string {
out := make(map[string]string)
for name, netCfg := range NetworksByName {
out[netCfg.L2ChainID.String()] = name
}
return out
}()
func AvailableNetworks() []string {
var networks []string
for name := range NetworksByName {
......
......@@ -109,7 +109,7 @@ func RollupNodeMain(ctx *cli.Context) error {
log.Error("Unable to create the rollup node", "error", err)
return err
}
log.Info("Starting rollup node")
log.Info("Starting rollup node", "version", VersionWithMeta)
if err := n.Start(context.Background()); err != nil {
log.Error("Unable to start rollup node", "error", err)
......
......@@ -13,6 +13,7 @@ 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"
......@@ -61,9 +62,11 @@ 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 intializing the rollup node", "err", err)
log.Error("Error initializing the rollup node", "err", err)
// ensure we always close the node resources if we fail to initialize the node.
if closeErr := n.Close(); closeErr != nil {
return nil, multierror.Append(err, closeErr)
......
......@@ -100,7 +100,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
l2Parent, nextL2Time, eth.ToBlockID(l1Info), l1Info.Time()))
}
l1InfoTx, err := L1InfoDepositBytes(seqNumber, l1Info, sysConfig)
l1InfoTx, err := L1InfoDepositBytes(seqNumber, l1Info, sysConfig, ba.cfg.IsRegolith(nextL2Time))
if err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
}
......
......@@ -66,7 +66,7 @@ func TestAttributesQueue(t *testing.T) {
l2Fetcher := &testutils.MockL2Client{}
l2Fetcher.ExpectSystemConfigByL2Hash(safeHead.Hash, parentL1Cfg, nil)
l1InfoTx, err := L1InfoDepositBytes(safeHead.SequenceNumber+1, l1Info, expectedL1Cfg)
l1InfoTx, err := L1InfoDepositBytes(safeHead.SequenceNumber+1, l1Info, expectedL1Cfg, false)
require.NoError(t, err)
attrs := eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(safeHead.Time + cfg.BlockTime),
......
......@@ -113,7 +113,7 @@ func TestPreparePayloadAttributes(t *testing.T) {
l1Info.InfoParentHash = l2Parent.L1Origin.Hash
l1Info.InfoNum = l2Parent.L1Origin.Number + 1
epoch := l1Info.ID()
l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg)
l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, false)
require.NoError(t, err)
l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil)
attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
......@@ -150,7 +150,7 @@ func TestPreparePayloadAttributes(t *testing.T) {
require.NoError(t, err)
epoch := l1Info.ID()
l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg)
l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, false)
require.NoError(t, err)
l2Txs := append(append(make([]eth.Data, 0), l1InfoTx), usedDepositTxs...)
......@@ -180,7 +180,7 @@ func TestPreparePayloadAttributes(t *testing.T) {
l1Info.InfoNum = l2Parent.L1Origin.Number
epoch := l1Info.ID()
l1InfoTx, err := L1InfoDepositBytes(l2Parent.SequenceNumber+1, l1Info, testSysCfg)
l1InfoTx, err := L1InfoDepositBytes(l2Parent.SequenceNumber+1, l1Info, testSysCfg, false)
require.NoError(t, err)
l1Fetcher.ExpectInfoByHash(epoch.Hash, l1Info, nil)
......@@ -195,6 +195,53 @@ func TestPreparePayloadAttributes(t *testing.T) {
require.Equal(t, l1InfoTx, []byte(attrs.Transactions[0]))
require.True(t, attrs.NoTxPool)
})
// Test that the payload attributes builder changes the deposit format based on L2-time-based regolith activation
t.Run("regolith", func(t *testing.T) {
testCases := []struct {
name string
l1Time uint64
l2ParentTime uint64
regolithTime uint64
regolith bool
}{
{"exactly", 900, 1000 - cfg.BlockTime, 1000, true},
{"almost", 900, 1000 - cfg.BlockTime - 1, 1000, false},
{"inactive", 700, 700, 1000, false},
{"l1 time before regolith", 1000, 1001, 1001, true},
{"l1 time way before regolith", 1000, 2000, 2000, true},
{"l1 time before regoltih and l2 after", 1000, 3000, 2000, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cfgCopy := *cfg // copy, we are making regolith config modifications
cfg := &cfgCopy
rng := rand.New(rand.NewSource(1234))
l1Fetcher := &testutils.MockL1Source{}
defer l1Fetcher.AssertExpectations(t)
l2Parent := testutils.RandomL2BlockRef(rng)
cfg.RegolithTime = &tc.regolithTime
l2Parent.Time = tc.l2ParentTime
l1CfgFetcher := &testutils.MockL2Client{}
l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
defer l1CfgFetcher.AssertExpectations(t)
l1Info := testutils.RandomBlockInfo(rng)
l1Info.InfoParentHash = l2Parent.L1Origin.Hash
l1Info.InfoNum = l2Parent.L1Origin.Number + 1
l1Info.InfoTime = tc.l1Time
epoch := l1Info.ID()
l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, tc.regolith)
require.NoError(t, err)
l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil)
attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
require.NoError(t, err)
require.Equal(t, l1InfoTx, []byte(attrs.Transactions[0]))
})
}
})
}
func encodeDeposits(deposits []*types.DepositTx) (out []eth.Data, err error) {
......
......@@ -26,6 +26,10 @@ var (
L1BlockAddress = predeploys.L1BlockAddr
)
const (
RegolithSystemTxGas = 1_000_000
)
// L1BlockInfo presents the information stored in a L1Block.setL1BlockValues call
type L1BlockInfo struct {
Number uint64
......@@ -115,7 +119,7 @@ func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) {
// L1InfoDeposit creates a L1 Info deposit transaction based on the L1 block,
// and the L2 block-height difference with the start of the epoch.
func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfig) (*types.DepositTx, error) {
func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfig, regolith bool) (*types.DepositTx, error) {
infoDat := L1BlockInfo{
Number: block.NumberU64(),
Time: block.Time(),
......@@ -137,7 +141,7 @@ func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfi
}
// Set a very large gas limit with `IsSystemTransaction` to ensure
// that the L1 Attributes Transaction does not run out of gas.
return &types.DepositTx{
out := &types.DepositTx{
SourceHash: source.SourceHash(),
From: L1InfoDepositerAddress,
To: &L1BlockAddress,
......@@ -146,12 +150,18 @@ func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfi
Gas: 150_000_000,
IsSystemTransaction: true,
Data: data,
}, nil
}
// With the regolith fork we disable the IsSystemTx functionality, and allocate real gas
if regolith {
out.IsSystemTransaction = false
out.Gas = RegolithSystemTxGas
}
return out, nil
}
// L1InfoDepositBytes returns a serialized L1-info attributes transaction.
func L1InfoDepositBytes(seqNumber uint64, l1Info eth.BlockInfo, sysCfg eth.SystemConfig) ([]byte, error) {
dep, err := L1InfoDeposit(seqNumber, l1Info, sysCfg)
func L1InfoDepositBytes(seqNumber uint64, l1Info eth.BlockInfo, sysCfg eth.SystemConfig, regolith bool) ([]byte, error) {
dep, err := L1InfoDeposit(seqNumber, l1Info, sysCfg, regolith)
if err != nil {
return nil, fmt.Errorf("failed to create L1 info tx: %w", err)
}
......
......@@ -65,7 +65,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) {
info := testCase.mkInfo(rng)
l1Cfg := testCase.mkL1Cfg(rng, info)
seqNr := testCase.seqNr(rng)
depTx, err := L1InfoDeposit(seqNr, info, l1Cfg)
depTx, err := L1InfoDeposit(seqNr, info, l1Cfg, false)
require.NoError(t, err)
res, err := L1InfoDepositTxData(depTx.Data)
require.NoError(t, err, "expected valid deposit info")
......@@ -95,11 +95,19 @@ func TestParseL1InfoDepositTxData(t *testing.T) {
t.Run("invalid selector", func(t *testing.T) {
rng := rand.New(rand.NewSource(1234))
info := testutils.MakeBlockInfo(nil)(rng)
depTx, err := L1InfoDeposit(randomSeqNr(rng), info, randomL1Cfg(rng, info))
depTx, err := L1InfoDeposit(randomSeqNr(rng), info, randomL1Cfg(rng, info), false)
require.NoError(t, err)
_, err = rand.Read(depTx.Data[0:4])
require.NoError(t, err)
_, err = L1InfoDepositTxData(depTx.Data)
require.ErrorContains(t, err, "function signature")
})
t.Run("regolith", func(t *testing.T) {
rng := rand.New(rand.NewSource(1234))
info := testutils.MakeBlockInfo(nil)(rng)
depTx, err := L1InfoDeposit(randomSeqNr(rng), info, randomL1Cfg(rng, info), true)
require.NoError(t, err)
require.False(t, depTx.IsSystemTransaction)
require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas))
})
}
......@@ -27,7 +27,7 @@ func FuzzParseL1InfoDepositTxDataValid(f *testing.F) {
typeProvider.Fuzz(&sysCfg)
// Create our deposit tx from our info
depTx, err := L1InfoDeposit(seqNr, &l1Info, sysCfg)
depTx, err := L1InfoDeposit(seqNr, &l1Info, sysCfg, false)
require.NoError(t, err, "error creating deposit tx from L1 info")
// Get our info from out deposit tx
......@@ -71,7 +71,7 @@ func FuzzDecodeDepositTxDataToL1Info(f *testing.F) {
GasLimit: uint64(0),
}
depTx, err := L1InfoDeposit(res.SequenceNumber, &l1Info, sysCfg)
depTx, err := L1InfoDeposit(res.SequenceNumber, &l1Info, sysCfg, false)
require.NoError(t, err, "error creating deposit tx from L1 info")
require.Equal(t, depTx.Data, fuzzedData)
})
......
......@@ -179,7 +179,8 @@ func (d *Sequencer) RunNextSequencerAction(ctx context.Context) *eth.ExecutionPa
d.log.Error("sequencer failed to start building new block", "err", err)
d.nextAction = d.timeNow().Add(time.Second)
} else {
d.log.Info("sequencer started building new block", "payload_id", buildingID)
parent, buildingID, _ := d.engine.BuildingPayload() // we should have a new payload ID now that we're building a block
d.log.Info("sequencer started building new block", "payload_id", buildingID, "l2_parent_block", parent, "l2_parent_block_time", parent.Time)
}
return nil
}
......
......@@ -245,7 +245,7 @@ func TestSequencerChaosMonkey(t *testing.T) {
InfoBaseFee: big.NewInt(1234),
InfoReceiptRoot: common.Hash{},
}
infoDep, err := derive.L1InfoDepositBytes(seqNr, l1Info, cfg.Genesis.SystemConfig)
infoDep, err := derive.L1InfoDepositBytes(seqNr, l1Info, cfg.Genesis.SystemConfig, false)
require.NoError(t, err)
testGasLimit := eth.Uint64Quantity(10_000_000)
......
......@@ -5,9 +5,11 @@ import (
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/eth"
)
......@@ -66,6 +68,12 @@ type Config struct {
// Required to identify the L2 network and create p2p signatures unique for this chain.
L2ChainID *big.Int `json:"l2_chain_id"`
// RegolithTime sets the activation time of the Regolith network-upgrade:
// a pre-mainnet Bedrock change that addresses findings of the Sherlock contest related to deposit attributes.
// "Regolith" is the loose deposited rock that sits on top of Bedrock.
// Active if RegolithTime != nil && L2 block timestamp >= *RegolithTime, inactive otherwise.
RegolithTime *uint64 `json:"regolith_time,omitempty"`
// Note: below addresses are part of the block-derivation process,
// and required to be the same network-wide to stay in consensus.
......@@ -228,4 +236,53 @@ func (c *Config) L1Signer() types.Signer {
return types.NewLondonSigner(c.L1ChainID)
}
// IsRegolith returns true if the Regolith hardfork is active at or past the given timestamp.
func (c *Config) IsRegolith(timestamp uint64) bool {
return c.RegolithTime != nil && timestamp >= *c.RegolithTime
}
// Description outputs a banner describing the important parts of rollup configuration in a human-readable form.
// 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) Description(l2Chains map[string]string) string {
// Find and report the network the user is running
var banner string
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"
}
banner += fmt.Sprintf("L2 Chain ID: %v (%s)\n", c.L2ChainID, networkL2)
banner += fmt.Sprintf("L1 Chain ID: %v (%s)\n", c.L1ChainID, networkL1)
// Report the genesis configuration
banner += "Bedrock starting point:\n"
banner += fmt.Sprintf(" L2 starting time: %d ~ %s\n", c.Genesis.L2Time, fmtTime(c.Genesis.L2Time))
banner += fmt.Sprintf(" L2 block: %s %d\n", c.Genesis.L2.Hash, c.Genesis.L2.Number)
banner += fmt.Sprintf(" L1 block: %s %d\n", c.Genesis.L1.Hash, c.Genesis.L1.Number)
// Report the upgrade configuration
banner += "Post-Bedrock Network Upgrades (timestamp based):\n"
banner += fmt.Sprintf(" - Regolith: %s\n", fmtForkTimeOrUnset(c.RegolithTime))
return banner
}
func fmtForkTimeOrUnset(v *uint64) string {
if v == nil {
return "(not configured)"
}
if *v == 0 { // don't output the unix epoch time if it's really just activated at genesis.
return "@ genesis"
}
return fmt.Sprintf("@ %-10v ~ %s", *v, fmtTime(*v))
}
func fmtTime(v uint64) string {
return time.Unix(int64(v), 0).Format(time.UnixDate)
}
type Epoch uint64
......@@ -3,12 +3,14 @@ package rollup
import (
"context"
"encoding/json"
"fmt"
"math/big"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
......@@ -136,6 +138,65 @@ func TestCheckL1BlockRefByNumber(t *testing.T) {
assert.Error(t, err)
}
// TestRandomConfigDescription tests that the description works for different variations of a random rollup config.
func TestRandomConfigDescription(t *testing.T) {
t.Run("named L2", func(t *testing.T) {
config := randConfig()
out := config.Description(map[string]string{config.L2ChainID.String(): "foobar chain"})
require.Contains(t, out, "foobar chain")
})
t.Run("named L1", func(t *testing.T) {
config := randConfig()
config.L1ChainID = big.NewInt(5)
out := config.Description(map[string]string{config.L2ChainID.String(): "foobar chain"})
require.Contains(t, out, "goerli")
})
t.Run("unnamed", func(t *testing.T) {
config := randConfig()
out := config.Description(nil)
require.Contains(t, out, "(unknown L1)")
require.Contains(t, out, "(unknown L2)")
})
t.Run("regolith unset", func(t *testing.T) {
config := randConfig()
config.RegolithTime = nil
out := config.Description(nil)
require.Contains(t, out, "Regolith: (not configured)")
})
t.Run("regolith genesis", func(t *testing.T) {
config := randConfig()
config.RegolithTime = new(uint64)
out := config.Description(nil)
require.Contains(t, out, "Regolith: @ genesis")
})
t.Run("regolith date", func(t *testing.T) {
config := randConfig()
x := uint64(1677119335)
config.RegolithTime = &x
out := config.Description(nil)
// Don't check human-readable part of the date, it's timezone-dependent.
// Don't make this test fail only in Australia :')
require.Contains(t, out, fmt.Sprintf("Regolith: @ %d ~ ", x))
})
}
// TestRegolithActivation tests the activation condition of the Regolith upgrade.
func TestRegolithActivation(t *testing.T) {
config := randConfig()
config.RegolithTime = nil
require.False(t, config.IsRegolith(0), "false if nil time, even if checking 0")
require.False(t, config.IsRegolith(123456), "false if nil time")
config.RegolithTime = new(uint64)
require.True(t, config.IsRegolith(0), "true at zero")
require.True(t, config.IsRegolith(123456), "true for any")
x := uint64(123)
config.RegolithTime = &x
require.False(t, config.IsRegolith(0))
require.False(t, config.IsRegolith(122))
require.True(t, config.IsRegolith(123))
require.True(t, config.IsRegolith(124))
}
type mockL2Client struct {
chainID *big.Int
Hash common.Hash
......
......@@ -16,7 +16,7 @@ WORKDIR /opt/foundry
# Only diff from upstream docker image is this clone instead
# of COPY. We select a specific commit to use.
RUN git clone https://github.com/foundry-rs/foundry.git . \
&& git checkout edf15abd648bb96e2bcee342c1d72ec7d1066cd1
&& git checkout 8f3fca9c608d58981daaffe11e7f8076644cb753
RUN source $HOME/.profile && \
cargo build --release && \
......
......@@ -9,7 +9,6 @@
"batch-submitter",
"bss-core",
"gas-oracle",
"indexer",
"integration-tests",
"l2geth-exporter",
"l2geth",
......@@ -19,7 +18,6 @@
"ops/docker/js-builder",
"ops/docker/ci-builder",
"ops/docker/foundry",
"proxyd",
"endpoint-monitor"
],
"nohoist": [
......
This diff is collapsed.
......@@ -63,6 +63,7 @@
| l2Sender | address | 50 | 0 | 20 | contracts/L1/OptimismPortal.sol:OptimismPortal |
| finalizedWithdrawals | mapping(bytes32 => bool) | 51 | 0 | 32 | contracts/L1/OptimismPortal.sol:OptimismPortal |
| provenWithdrawals | mapping(bytes32 => struct OptimismPortal.ProvenWithdrawal) | 52 | 0 | 32 | contracts/L1/OptimismPortal.sol:OptimismPortal |
| paused | bool | 53 | 0 | 1 | contracts/L1/OptimismPortal.sol:OptimismPortal |
=======================
➡ contracts/L1/SystemConfig.sol:SystemConfig
......
......@@ -26,7 +26,7 @@ contract L1ERC721Bridge is ERC721Bridge, Semver {
* @param _otherBridge Address of the ERC721 bridge on the other network.
*/
constructor(address _messenger, address _otherBridge)
Semver(1, 0, 0)
Semver(1, 1, 0)
ERC721Bridge(_messenger, _otherBridge)
{}
......@@ -83,7 +83,7 @@ contract L1ERC721Bridge is ERC721Bridge, Semver {
uint32 _minGasLimit,
bytes calldata _extraData
) internal override {
require(_remoteToken != address(0), "ERC721Bridge: remote token cannot be address(0)");
require(_remoteToken != address(0), "L1ERC721Bridge: remote token cannot be address(0)");
// Construct calldata for _l2Token.finalizeBridgeERC721(_to, _tokenId)
bytes memory message = abi.encodeWithSelector(
......
......@@ -107,28 +107,6 @@ contract L1StandardBridge is StandardBridge, Semver {
_initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes(""));
}
/**
* @custom:legacy
* @notice Finalizes a withdrawal of ERC20 tokens from L2.
*
* @param _l1Token Address of the token on L1.
* @param _l2Token Address of the corresponding token on L2.
* @param _from Address of the withdrawer on L2.
* @param _to Address of the recipient on L1.
* @param _amount Amount of the ERC20 to withdraw.
* @param _extraData Optional data forwarded from L2.
*/
function finalizeERC20Withdrawal(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _extraData
) external onlyOtherBridge {
finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData);
}
/**
* @custom:legacy
* @notice Deposits some amount of ETH into the sender's account on L2.
......@@ -240,10 +218,32 @@ contract L1StandardBridge is StandardBridge, Semver {
address _to,
uint256 _amount,
bytes calldata _extraData
) external payable onlyOtherBridge {
) external payable {
finalizeBridgeETH(_from, _to, _amount, _extraData);
}
/**
* @custom:legacy
* @notice Finalizes a withdrawal of ERC20 tokens from L2.
*
* @param _l1Token Address of the token on L1.
* @param _l2Token Address of the corresponding token on L2.
* @param _from Address of the withdrawer on L2.
* @param _to Address of the recipient on L1.
* @param _amount Amount of the ERC20 to withdraw.
* @param _extraData Optional data forwarded from L2.
*/
function finalizeERC20Withdrawal(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _extraData
) external {
finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData);
}
/**
* @custom:legacy
* @notice Retrieves the access of the corresponding L2 bridge contract.
......
......@@ -58,6 +58,11 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
*/
L2OutputOracle public immutable L2_ORACLE;
/**
* @notice Address that has the ability to pause and unpause deposits and withdrawals.
*/
address public immutable GUARDIAN;
/**
* @notice Address of the L2 account which initiated a withdrawal in this transaction. If the
* of this variable is the default L2 sender address, then we are NOT inside of a call
......@@ -75,6 +80,13 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
*/
mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;
/**
* @notice Determines if cross domain messaging is paused. When set to true,
* deposits and withdrawals are paused. This may be removed in the
* future.
*/
bool public paused;
/**
* @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event
* are read by the rollup node and used to derive deposit transactions on L2.
......@@ -111,25 +123,74 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
/**
* @custom:semver 1.0.0
* @notice Emitted when the pause is triggered.
*
* @param account Address of the account triggering the pause.
*/
event Paused(address account);
/**
* @notice Emitted when the pause is lifted.
*
* @param account Address of the account triggering the unpause.
*/
event Unpaused(address account);
/**
* @notice Reverts when paused.
*/
modifier whenNotPaused() {
require(paused == false, "OptimismPortal: paused");
_;
}
/**
* @custom:semver 1.1.0
*
* @param _l2Oracle Address of the L2OutputOracle contract.
* @param _guardian Address that can pause deposits and withdrawals.
* @param _finalizationPeriodSeconds Output finalization time in seconds.
* @param _paused Sets the contract's pausability state.
*/
constructor(L2OutputOracle _l2Oracle, uint256 _finalizationPeriodSeconds) Semver(1, 0, 0) {
constructor(
L2OutputOracle _l2Oracle,
uint256 _finalizationPeriodSeconds,
address _guardian,
bool _paused
) Semver(1, 1, 0) {
L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian;
FINALIZATION_PERIOD_SECONDS = _finalizationPeriodSeconds;
initialize();
initialize(_paused);
}
/**
* @notice Initializer.
*/
function initialize() public initializer {
function initialize(bool _paused) public initializer {
l2Sender = Constants.DEFAULT_L2_SENDER;
paused = _paused;
__ResourceMetering_init();
}
/**
* @notice Pause deposits and withdrawals.
*/
function pause() external {
require(msg.sender == GUARDIAN, "OptimismPortal: only guardian can pause");
paused = true;
emit Paused(msg.sender);
}
/**
* @notice Unpause deposits and withdrawals.
*/
function unpause() external {
require(msg.sender == GUARDIAN, "OptimismPortal: only guardian can unpause");
paused = false;
emit Unpaused(msg.sender);
}
/**
* @notice Accepts value so that users can send ETH directly to this contract and have the
* funds be deposited to their address on L2. This is intended as a convenience
......@@ -162,7 +223,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
) external {
) external whenNotPaused {
// Prevent users from creating a deposit transaction where this address is the message
// sender on L2. Because this is checked here, we do not need to check again in
// `finalizeWithdrawalTransaction`.
......@@ -240,7 +301,10 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
*
* @param _tx Withdrawal transaction to finalize.
*/
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external {
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx)
external
whenNotPaused
{
// Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
// than the default value when a withdrawal transaction is being finalized. This check is
// a defacto reentrancy guard.
......
......@@ -92,8 +92,7 @@ abstract contract ResourceMetering is Initializable {
// spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - TARGET_RESOURCE_LIMIT;
int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) /
TARGET_RESOURCE_LIMIT /
BASE_FEE_MAX_CHANGE_DENOMINATOR;
(TARGET_RESOURCE_LIMIT * BASE_FEE_MAX_CHANGE_DENOMINATOR);
// Update base fee by adding the base fee delta and clamp the resulting value between
// min and max.
......
......@@ -26,7 +26,7 @@ contract L2ERC721Bridge is ERC721Bridge, Semver {
* @param _otherBridge Address of the ERC721 bridge on the other network.
*/
constructor(address _messenger, address _otherBridge)
Semver(1, 0, 0)
Semver(1, 1, 0)
ERC721Bridge(_messenger, _otherBridge)
{}
......@@ -85,12 +85,12 @@ contract L2ERC721Bridge is ERC721Bridge, Semver {
uint32 _minGasLimit,
bytes calldata _extraData
) internal override {
require(_remoteToken != address(0), "ERC721Bridge: remote token cannot be address(0)");
require(_remoteToken != address(0), "L2ERC721Bridge: remote token cannot be address(0)");
// Check that the withdrawal is being initiated by the NFT owner
require(
_from == IOptimismMintableERC721(_localToken).ownerOf(_tokenId),
"Withdrawal is not being initiated by NFT owner"
"L2ERC721Bridge: Withdrawal is not being initiated by NFT owner"
);
// Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId)
......
......@@ -111,6 +111,12 @@ contract SystemDictator is OwnableUpgradeable {
*/
L2OutputOracleDynamicConfig public l2OutputOracleDynamicConfig;
/**
* @notice Dynamic configuration for the OptimismPortal. Determines
* if the system should be paused when initialized.
*/
bool public optimismPortalDynamicConfig;
/**
* @notice Current step;
*/
......@@ -160,14 +166,17 @@ contract SystemDictator is OwnableUpgradeable {
}
/**
* @notice Allows the owner to update dynamic L2OutputOracle config.
* @notice Allows the owner to update dynamic config.
*
* @param _l2OutputOracleDynamicConfig Dynamic L2OutputOracle config.
* @param _optimismPortalDynamicConfig Dynamic OptimismPortal config.
*/
function updateL2OutputOracleDynamicConfig(
L2OutputOracleDynamicConfig memory _l2OutputOracleDynamicConfig
function updateDynamicConfig(
L2OutputOracleDynamicConfig memory _l2OutputOracleDynamicConfig,
bool _optimismPortalDynamicConfig
) external onlyOwner {
l2OutputOracleDynamicConfig = _l2OutputOracleDynamicConfig;
optimismPortalDynamicConfig = _optimismPortalDynamicConfig;
dynamicConfigSet = true;
}
......@@ -314,7 +323,7 @@ contract SystemDictator is OwnableUpgradeable {
config.globalConfig.proxyAdmin.upgradeAndCall(
payable(config.proxyAddressConfig.optimismPortalProxy),
address(config.implementationAddressConfig.optimismPortalImpl),
abi.encodeCall(OptimismPortal.initialize, ())
abi.encodeCall(OptimismPortal.initialize, (optimismPortalDynamicConfig))
);
// Upgrade the L1CrossDomainMessenger.
......
......@@ -9,7 +9,12 @@ contract EchidnaFuzzOptimismPortal {
bool internal failedToComplete;
constructor() {
portal = new OptimismPortal(L2OutputOracle(address(0)), 10);
portal = new OptimismPortal({
_l2Oracle: L2OutputOracle(address(0)),
_guardian: address(0),
_finalizationPeriodSeconds: 10,
_paused: false
});
}
// A test intended to identify any unexpected halting conditions
......
......@@ -50,7 +50,7 @@ contract CommonTest is Test {
FFIInterface ffi;
function _setUp() public {
function setUp() public virtual {
// Give alice and bob some ETH
vm.deal(alice, 1 << 16);
vm.deal(bob, 1 << 16);
......@@ -99,6 +99,7 @@ contract L2OutputOracle_Initializer is CommonTest {
uint256 internal l2BlockTime = 2;
uint256 internal startingBlockNumber = 200;
uint256 internal startingTimestamp = 1000;
address guardian;
// Test data
uint256 initL1Time;
......@@ -117,8 +118,9 @@ contract L2OutputOracle_Initializer is CommonTest {
vm.warp(oracle.computeL2Timestamp(_nextBlockNumber) + 1);
}
function setUp() public virtual {
_setUp();
function setUp() public virtual override {
super.setUp();
guardian = makeAddr("guardian");
// By default the first block has timestamp and number zero, which will cause underflows in the
// tests, so we'll move forward to these block values.
......@@ -163,14 +165,19 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
);
function setUp() public virtual override {
L2OutputOracle_Initializer.setUp();
super.setUp();
opImpl = new OptimismPortal(oracle, 7 days);
opImpl = new OptimismPortal({
_l2Oracle: oracle,
_guardian: guardian,
_finalizationPeriodSeconds: 7 days,
_paused: true
});
Proxy proxy = new Proxy(multisig);
vm.prank(multisig);
proxy.upgradeToAndCall(
address(opImpl),
abi.encodeWithSelector(OptimismPortal.initialize.selector)
abi.encodeWithSelector(OptimismPortal.initialize.selector, false)
);
op = OptimismPortal(payable(address(proxy)));
}
......@@ -224,7 +231,12 @@ contract Messenger_Initializer is L2OutputOracle_Initializer {
super.setUp();
// Deploy the OptimismPortal
op = new OptimismPortal(oracle, 7 days);
op = new OptimismPortal({
_l2Oracle: oracle,
_guardian: guardian,
_finalizationPeriodSeconds: 7 days,
_paused: false
});
vm.label(address(op), "OptimismPortal");
// Deploy the address manager
......
......@@ -18,7 +18,8 @@ contract XDomainSetter is CrossDomainOwnable {
contract CrossDomainOwnable_Test is CommonTest {
XDomainSetter setter;
function setUp() external {
function setUp() public override {
super.setUp();
setter = new XDomainSetter();
}
......
......@@ -7,7 +7,7 @@ import { DeployerWhitelist } from "../legacy/DeployerWhitelist.sol";
contract DeployerWhitelist_Test is CommonTest {
DeployerWhitelist list;
function setUp() external {
function setUp() public virtual override {
list = new DeployerWhitelist();
}
......
......@@ -6,10 +6,6 @@ import { Types } from "../libraries/Types.sol";
import { Encoding } from "../libraries/Encoding.sol";
contract Encoding_Test is CommonTest {
function setUp() external {
_setUp();
}
function testFuzz_nonceVersioning_succeeds(uint240 _nonce, uint16 _version) external {
(uint240 nonce, uint16 version) = Encoding.decodeVersionedNonce(
Encoding.encodeVersionedNonce(_nonce, _version)
......
......@@ -25,7 +25,8 @@ contract GasPriceOracle_Test is CommonTest {
uint256 constant l1FeeOverhead = 310;
uint256 constant l1FeeScalar = 10;
function setUp() external {
function setUp() public virtual override {
super.setUp();
// place the L1Block contract at the predeploy address
vm.etch(Predeploys.L1_BLOCK_ATTRIBUTES, address(new L1Block()).code);
......
......@@ -9,7 +9,8 @@ contract GovernanceToken_Test is CommonTest {
address constant rando = address(0x5678);
GovernanceToken internal gov;
function setUp() external {
function setUp() public virtual override {
super.setUp();
vm.prank(owner);
gov = new GovernanceToken();
}
......
......@@ -7,10 +7,6 @@ import { Hashing } from "../libraries/Hashing.sol";
import { Encoding } from "../libraries/Encoding.sol";
contract Hashing_hashDepositSource_Test is CommonTest {
function setUp() external {
_setUp();
}
/**
* @notice Tests that hashDepositSource returns the correct hash in a simple case.
*/
......@@ -26,10 +22,6 @@ contract Hashing_hashDepositSource_Test is CommonTest {
}
contract Hashing_hashCrossDomainMessage_Test is CommonTest {
function setUp() external {
_setUp();
}
/**
* @notice Tests that hashCrossDomainMessage returns the correct hash in a simple case.
*/
......@@ -54,10 +46,6 @@ contract Hashing_hashCrossDomainMessage_Test is CommonTest {
}
contract Hashing_hashWithdrawal_Test is CommonTest {
function setUp() external {
_setUp();
}
/**
* @notice Tests that hashWithdrawal returns the correct hash in a simple case.
*/
......@@ -79,10 +67,6 @@ contract Hashing_hashWithdrawal_Test is CommonTest {
}
contract Hashing_hashOutputRootProof_Test is CommonTest {
function setUp() external {
_setUp();
}
/**
* @notice Tests that hashOutputRootProof returns the correct hash in a simple case.
*/
......@@ -112,10 +96,6 @@ contract Hashing_hashOutputRootProof_Test is CommonTest {
}
contract Hashing_hashDepositTransaction_Test is CommonTest {
function setUp() external {
_setUp();
}
/**
* @notice Tests that hashDepositTransaction returns the correct hash in a simple case.
*/
......
......@@ -9,7 +9,8 @@ contract L1BlockTest is CommonTest {
address depositor;
bytes32 immutable NON_ZERO_HASH = keccak256(abi.encode(1));
function setUp() external {
function setUp() public virtual override {
super.setUp();
lb = new L1Block();
depositor = lb.DEPOSITOR_ACCOUNT();
vm.prank(depositor);
......
......@@ -135,7 +135,7 @@ contract L1ERC721Bridge_Test is Messenger_Initializer {
function test_bridgeERC721_remoteTokenZeroAddress_reverts() external {
// Bridge the token.
vm.prank(alice);
vm.expectRevert("ERC721Bridge: remote token cannot be address(0)");
vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)");
bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678");
// Token is not locked in the bridge.
......@@ -212,7 +212,7 @@ contract L1ERC721Bridge_Test is Messenger_Initializer {
function test_bridgeERC721To_remoteTokenZeroAddress_reverts() external {
// Bridge the token.
vm.prank(alice);
vm.expectRevert("ERC721Bridge: remote token cannot be address(0)");
vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)");
bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, 1234, hex"5678");
// Token is not locked in the bridge.
......
......@@ -144,7 +144,7 @@ contract L2ERC721Bridge_Test is Messenger_Initializer {
function test_bridgeERC721_remoteTokenZeroAddress_reverts() external {
// Bridge the token.
vm.prank(alice);
vm.expectRevert("ERC721Bridge: remote token cannot be address(0)");
vm.expectRevert("L2ERC721Bridge: remote token cannot be address(0)");
bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678");
// Token is not locked in the bridge.
......@@ -154,7 +154,7 @@ contract L2ERC721Bridge_Test is Messenger_Initializer {
function test_bridgeERC721_wrongOwner_reverts() external {
// Bridge the token.
vm.prank(bob);
vm.expectRevert("Withdrawal is not being initiated by NFT owner");
vm.expectRevert("L2ERC721Bridge: Withdrawal is not being initiated by NFT owner");
bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678");
// Token is not locked in the bridge.
......@@ -218,7 +218,7 @@ contract L2ERC721Bridge_Test is Messenger_Initializer {
function test_bridgeERC721To_remoteTokenZeroAddress_reverts() external {
// Bridge the token.
vm.prank(alice);
vm.expectRevert("ERC721Bridge: remote token cannot be address(0)");
vm.expectRevert("L2ERC721Bridge: remote token cannot be address(0)");
bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, 1234, hex"5678");
// Token is not locked in the bridge.
......@@ -228,7 +228,7 @@ contract L2ERC721Bridge_Test is Messenger_Initializer {
function test_bridgeERC721To_wrongOwner_reverts() external {
// Bridge the token.
vm.prank(bob);
vm.expectRevert("Withdrawal is not being initiated by NFT owner");
vm.expectRevert("L2ERC721Bridge: Withdrawal is not being initiated by NFT owner");
bridge.bridgeERC721To(
address(localToken),
address(remoteToken),
......
......@@ -21,7 +21,8 @@ contract L2ToL1MessagePasserTest is CommonTest {
event WithdrawerBalanceBurnt(uint256 indexed amount);
function setUp() public virtual {
function setUp() public virtual override {
super.setUp();
messagePasser = new L2ToL1MessagePasser();
}
......
......@@ -8,7 +8,8 @@ import { Predeploys } from "../libraries/Predeploys.sol";
contract LegacyERC20ETH_Test is CommonTest {
LegacyERC20ETH eth;
function setUp() external {
function setUp() public virtual override {
super.setUp();
eth = new LegacyERC20ETH();
}
......
......@@ -8,7 +8,8 @@ import { Predeploys } from "../libraries/Predeploys.sol";
contract LegacyMessagePasser_Test is CommonTest {
LegacyMessagePasser messagePasser;
function setUp() external {
function setUp() public virtual override {
super.setUp();
messagePasser = new LegacyMessagePasser();
}
......
......@@ -5,10 +5,6 @@ import { CommonTest } from "./CommonTest.t.sol";
import { MerkleTrie } from "../libraries/trie/MerkleTrie.sol";
contract MerkleTrie_get_Test is CommonTest {
function setUp() public {
_setUp();
}
function test_get_validProof1_succeeds() external {
bytes32 root = 0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f;
bytes memory key = hex"6b6579326262";
......
......@@ -11,7 +11,9 @@ contract MintManager_Initializer is CommonTest {
GovernanceToken internal gov;
MintManager internal manager;
function setUp() external {
function setUp() public virtual override {
super.setUp();
vm.prank(owner);
gov = new GovernanceToken();
......
......@@ -11,10 +11,83 @@ import { Hashing } from "../libraries/Hashing.sol";
import { Proxy } from "../universal/Proxy.sol";
contract OptimismPortal_Test is Portal_Initializer {
event Paused(address);
event Unpaused(address);
function test_constructor_succeeds() external {
assertEq(op.FINALIZATION_PERIOD_SECONDS(), 7 days);
assertEq(address(op.L2_ORACLE()), address(oracle));
assertEq(op.l2Sender(), 0x000000000000000000000000000000000000dEaD);
assertEq(op.paused(), false);
}
/**
* @notice The OptimismPortal can be paused by the GUARDIAN
*/
function test_pause_succeeds() external {
address guardian = op.GUARDIAN();
assertEq(op.paused(), false);
vm.expectEmit(true, true, true, true, address(op));
emit Paused(guardian);
vm.prank(guardian);
op.pause();
assertEq(op.paused(), true);
}
/**
* @notice The OptimismPortal reverts when an account that is not the
* GUARDIAN calls `pause()`
*/
function test_pause_onlyGuardian_reverts() external {
assertEq(op.paused(), false);
assertTrue(op.GUARDIAN() != alice);
vm.expectRevert("OptimismPortal: only guardian can pause");
vm.prank(alice);
op.pause();
assertEq(op.paused(), false);
}
/**
* @notice The OptimismPortal can be unpaused by the GUARDIAN
*/
function test_unpause_succeeds() external {
address guardian = op.GUARDIAN();
vm.prank(guardian);
op.pause();
assertEq(op.paused(), true);
vm.expectEmit(true, true, true, true, address(op));
emit Unpaused(guardian);
vm.prank(guardian);
op.unpause();
assertEq(op.paused(), false);
}
/**
* @notice The OptimismPortal reverts when an account that is not
* the GUARDIAN calls `unpause()`
*/
function test_unpause_onlyGuardian_reverts() external {
address guardian = op.GUARDIAN();
vm.prank(guardian);
op.pause();
assertEq(op.paused(), true);
assertTrue(op.GUARDIAN() != alice);
vm.expectRevert("OptimismPortal: only guardian can unpause");
vm.prank(alice);
op.unpause();
assertEq(op.paused(), true);
}
function test_receive_succeeds() external {
......@@ -343,6 +416,22 @@ contract OptimismPortal_FinalizeWithdrawal_Test is Portal_Initializer {
assertFalse(op.finalizedWithdrawals(Hashing.hashWithdrawal(_defaultTx)));
}
/**
* @notice Proving withdrawal transactions should revert when paused
*/
function test_proveWithdrawalTransaction_paused_reverts() external {
vm.prank(op.GUARDIAN());
op.pause();
vm.expectRevert("OptimismPortal: paused");
op.proveWithdrawalTransaction({
_tx: _defaultTx,
_l2OutputIndex: _proposedOutputIndex,
_outputRootProof: _outputRootProof,
_withdrawalProof: _withdrawalProof
});
}
// Test: proveWithdrawalTransaction cannot prove a withdrawal with itself (the OptimismPortal) as the target.
function test_proveWithdrawalTransaction_onSelfCall_reverts() external {
_defaultTx.target = address(op);
......@@ -539,6 +628,17 @@ contract OptimismPortal_FinalizeWithdrawal_Test is Portal_Initializer {
assert(address(bob).balance == bobBalanceBefore + 100);
}
/**
* @notice Finalizing withdrawal transactions should revert when paused
*/
function test_finalizeWithdrawalTransaction_paused_reverts() external {
vm.prank(op.GUARDIAN());
op.pause();
vm.expectRevert("OptimismPortal: paused");
op.finalizeWithdrawalTransaction(_defaultTx);
}
// Test: finalizeWithdrawalTransaction reverts if the withdrawal has not been proven.
function test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() external {
uint256 bobBalanceBefore = address(bob).balance;
......@@ -956,12 +1056,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer {
function test_initialize_cannotInitProxy_reverts() external {
vm.expectRevert("Initializable: contract is already initialized");
OptimismPortal(payable(proxy)).initialize();
OptimismPortal(payable(proxy)).initialize(false);
}
function test_initialize_cannotInitImpl_reverts() external {
vm.expectRevert("Initializable: contract is already initialized");
OptimismPortal(opImpl).initialize();
OptimismPortal(opImpl).initialize(false);
}
function test_upgradeToAndCall_upgrading_succeeds() external {
......
......@@ -21,8 +21,8 @@ contract ResourceMetering_Test is CommonTest {
MeterUser internal meter;
uint64 initialBlockNum;
function setUp() external {
_setUp();
function setUp() public virtual override {
super.setUp();
meter = new MeterUser();
initialBlockNum = uint64(block.number);
}
......
......@@ -19,7 +19,7 @@ contract Semver_Test is CommonTest {
/**
* @notice Deploy a Semver contract
*/
function setUp() external {
function setUp() public virtual override {
semver = new Semver(7, 8, 0);
}
......
......@@ -28,13 +28,13 @@ contract SequencerFeeVault_Test is Bridge_Initializer {
}
function test_receive_succeeds() external {
assertEq(address(vault).balance, 0);
uint256 balance = address(vault).balance;
vm.prank(alice);
(bool success, ) = address(vault).call{ value: 100 }(hex"");
assertEq(success, true);
assertEq(address(vault).balance, 100);
assertEq(address(vault).balance, balance + 100);
}
function test_withdraw_notEnough_reverts() external {
......
......@@ -7,7 +7,8 @@ import { SystemConfig } from "../L1/SystemConfig.sol";
contract SystemConfig_Init is CommonTest {
SystemConfig sysConf;
function setUp() external {
function setUp() public virtual override {
super.setUp();
sysConf = new SystemConfig({
_owner: alice,
_overhead: 2100,
......
......@@ -94,7 +94,7 @@ contract RelayActor is StdUtils {
contract XDM_MinGasLimits is Messenger_Initializer {
RelayActor actor;
function setUp() public override {
function setUp() public virtual override {
// Set up the `L1CrossDomainMessenger` and `OptimismPortal` contracts.
super.setUp();
......
......@@ -2,9 +2,9 @@
"l1ChainID": 900,
"l2ChainID": 901,
"l2BlockTime": 2,
"maxSequencerDrift": 10,
"sequencerWindowSize": 15,
"channelTimeout": 40,
"maxSequencerDrift": 300,
"sequencerWindowSize": 200,
"channelTimeout": 120,
"p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"batchInboxAddress": "0xff00000000000000000000000000000000000000",
"batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
......
......@@ -8,17 +8,42 @@ import {
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const isLiveDeployer =
deployer.toLowerCase() === hre.deployConfig.controller.toLowerCase()
const L2OutputOracleProxy = await getContractFromArtifact(
hre,
'L2OutputOracleProxy'
)
const finalSystemOwner = hre.deployConfig.finalSystemOwner
const finalSystemOwnerCode = await hre.ethers.provider.getCode(
finalSystemOwner
)
if (finalSystemOwnerCode === '0x') {
console.log(
`WARNING: setting OptimismPortal.GUARDIAN to ${finalSystemOwner} and it has no code`
)
if (!isLiveDeployer) {
throw new Error(
'Do not deploy to production networks without the GUARDIAN being a contract'
)
}
}
// Deploy the OptimismPortal implementation as paused to
// ensure that users do not interact with it and instead
// interact with the proxied contract.
// The `finalSystemOwner` is set at the GUARDIAN.
await deploy({
hre,
name: 'OptimismPortal',
args: [
L2OutputOracleProxy.address,
hre.deployConfig.finalizationPeriodSeconds,
finalSystemOwner,
true, // paused
],
postDeployAction: async (contract) => {
await assertContractVariable(
......@@ -31,6 +56,11 @@ const deployFn: DeployFunction = async (hre) => {
'FINALIZATION_PERIOD_SECONDS',
hre.deployConfig.finalizationPeriodSeconds
)
await assertContractVariable(
contract,
'GUARDIAN',
hre.deployConfig.finalSystemOwner
)
},
})
}
......
......@@ -171,20 +171,23 @@ const deployFn: DeployFunction = async (hre) => {
deployL2StartingTimestamp = l1StartingBlock.timestamp
}
await SystemDictator.updateL2OutputOracleDynamicConfig({
await SystemDictator.updateDynamicConfig(
{
l2OutputOracleStartingBlockNumber:
hre.deployConfig.l2OutputOracleStartingBlockNumber,
l2OutputOracleStartingTimestamp: deployL2StartingTimestamp,
})
},
false // do not pause the the OptimismPortal when initializing
)
} else {
const tx =
await SystemDictator.populateTransaction.updateL2OutputOracleDynamicConfig(
const tx = await SystemDictator.populateTransaction.updateDynamicConfig(
{
l2OutputOracleStartingBlockNumber:
hre.deployConfig.l2OutputOracleStartingBlockNumber,
l2OutputOracleStartingTimestamp:
hre.deployConfig.l2OutputOracleStartingTimestamp,
}
},
true
)
console.log(`Please update dynamic oracle config...`)
console.log(`MSD address: ${SystemDictator.address}`)
......@@ -243,6 +246,12 @@ const deployFn: DeployFunction = async (hre) => {
(await hre.ethers.provider.getBalance(L1StandardBridge.address)).eq(0)
)
if (isLiveDeployer) {
await assertContractVariable(OptimismPortal, 'paused', false)
} else {
await assertContractVariable(OptimismPortal, 'paused', true)
}
// Check L1CrossDomainMessenger was initialized properly.
await assertContractVariable(L1CrossDomainMessenger, 'paused', true)
try {
......@@ -283,6 +292,35 @@ const deployFn: DeployFunction = async (hre) => {
},
})
if (await isStep(SystemDictator, 6)) {
console.log(`
Unpause the OptimismPortal. The GUARDIAN account should be used. In practice
this is the multisig. In test networks, the OptimismPortal is initialized
without being paused.
`)
if (isLiveDeployer) {
console.log('WARNING: OptimismPortal configured to not be paused')
console.log('This should only happen for test environments')
await assertContractVariable(OptimismPortal, 'paused', false)
} else {
const tx = await OptimismPortal.populateTransaction.unpause()
console.log(`Please unpause the OptimismPortal...`)
console.log(`OptimismPortal address: ${OptimismPortal.address}`)
console.log(`JSON:`)
console.log(jsonifyTransaction(tx))
}
await awaitCondition(
async () => {
const paused = await OptimismPortal.paused()
return !paused
},
30000,
1000
)
}
// Step 6 unpauses the new L1CrossDomainMessenger.
await doStep({
isLiveDeployer,
......
......@@ -19,6 +19,6 @@ This invariant asserts that there is no chain of calls that can be made that wil
## Deposits of any value should always succeed unless `_to` = `address(0)` or `_isCreation` = `true`.
**Test:** [`FuzzOptimismPortal.sol#L37`](../contracts/echidna/FuzzOptimismPortal.sol#L37)
**Test:** [`FuzzOptimismPortal.sol#L42`](../contracts/echidna/FuzzOptimismPortal.sol#L42)
All deposits, barring creation transactions and transactions sent to `address(0)`, should always succeed.
......@@ -79,7 +79,7 @@
"dotenv": "^16.0.0",
"ds-test": "https://github.com/dapphub/ds-test.git#9310e879db8ba3ea6d5c6489a579118fd264a3f5",
"ethereum-waffle": "^3.0.0",
"forge-std": "https://github.com/foundry-rs/forge-std.git#a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df",
"forge-std": "https://github.com/foundry-rs/forge-std.git#fd86115ed6aba8e234ee0fb86c12fe35eff0b2a0",
"glob": "^7.1.6",
"hardhat-deploy": "^0.11.4",
"solhint": "^3.3.7",
......
......@@ -525,7 +525,7 @@ const check = {
signer
)
await assertSemver(L2ERC721Bridge, 'L2ERC721Bridge')
await assertSemver(L2ERC721Bridge, 'L2ERC721Bridge', '1.1.0')
const MESSENGER = await L2ERC721Bridge.MESSENGER()
assert(MESSENGER !== hre.ethers.constants.AddressZero)
......
......@@ -30,7 +30,7 @@ export const advancedQueryFilter = async (
const allEvents = await contract.queryFilter(
options.queryFilter,
i,
i + step
Math.min(i + step, end)
)
const matching = options.filter
? allEvents.filter(options.filter)
......
{
"name": "@eth-optimism/proxyd",
"version": "3.14.1",
"private": true,
"dependencies": {}
}
......@@ -9518,9 +9518,9 @@ forever-agent@~0.6.1:
version "1.2.0"
resolved "https://github.com/foundry-rs/forge-std.git#53331f4cb2e313466f72440f3e73af048c454d02"
"forge-std@https://github.com/foundry-rs/forge-std.git#a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df":
"forge-std@https://github.com/foundry-rs/forge-std.git#fd86115ed6aba8e234ee0fb86c12fe35eff0b2a0":
version "1.4.0"
resolved "https://github.com/foundry-rs/forge-std.git#a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df"
resolved "https://github.com/foundry-rs/forge-std.git#fd86115ed6aba8e234ee0fb86c12fe35eff0b2a0"
form-data@^2.2.0:
version "2.5.1"
......
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