Commit 2198e3bb authored by Zach Howard's avatar Zach Howard Committed by GitHub

Merge pull request #4071 from ethereum-optimism/develop

Trigger releases
parents 2b3b09be a6db381a
---
'@eth-optimism/contracts-bedrock': patch
---
Fixes a severe vulnerability found in ToB's November 2022 audit of the Bedrock contracts
---
'@eth-optimism/contracts-bedrock': patch
---
Removes historicalTotalBlocks from the L2OutputOracle
---
'@eth-optimism/contracts-bedrock': minor
---
Deleted Unused Variables fundAccount , impersonatedTx
---
'@eth-optimism/contracts-bedrock': patch
---
Updates L2OutputOracle to easily delete multiple outputs at once
---
'@eth-optimism/indexer': patch
'@eth-optimism/contracts-bedrock': patch
'@eth-optimism/contracts-periphery': patch
---
Updated forge-std version
---
"@eth-optimism/l2geth-exporter": patch
---
Fix: Adding proper debug output for L1 CTC Address env var in l2geth-exporter
---
'@eth-optimism/indexer': minor
'@eth-optimism/contracts-bedrock': minor
'@eth-optimism/integration-tests-bedrock': minor
'@eth-optimism/sdk': minor
---
Adds an implementation of the Two Step Withdrawals V2 proposal
---
'@eth-optimism/contracts-bedrock': patch
---
Allows owner and proposer addresses to be the same in L2OutputOracle
This diff is collapsed.
......@@ -31,7 +31,6 @@ jobs:
l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
indexer: ${{ steps.packages.outputs.indexer }}
teleportr: ${{ steps.packages.outputs.teleportr }}
endpoint-monitor: ${{ steps.packages.outputs.l2geth-exporter }}
steps:
......@@ -568,43 +567,6 @@ jobs:
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
teleportr:
name: Publish Teleportr Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.teleportr != ''
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 ./teleportr/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./teleportr/Dockerfile
push: true
tags: ethereumoptimism/teleportr:${{ needs.canary-publish.outputs.teleportr }}
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
......
......@@ -26,7 +26,6 @@ jobs:
l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
indexer: ${{ steps.packages.outputs.indexer }}
teleportr: ${{ steps.packages.outputs.teleportr }}
ci-builder: ${{ steps.packages.outputs.ci-builder }}
foundry: ${{ steps.packages.outputs.foundry }}
endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }}
......@@ -600,43 +599,6 @@ jobs:
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
teleportr:
name: Publish Teleportr Version ${{ needs.release.outputs.teleportr }}
needs: release
if: needs.release.outputs.teleportr != ''
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 ./teleportr/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Publish Teleportr
uses: docker/build-push-action@v2
with:
context: .
file: ./teleportr/Dockerfile
push: true
tags: ethereumoptimism/teleportr:${{ needs.release.outputs.teleportr }},ethereumoptimism/teleportr: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
......
......@@ -73,7 +73,6 @@ Refer to the Directory Structure section below to understand which packages are
├── <a href="./op-exporter">op-exporter</a>: A prometheus exporter to collect/serve metrics from an Optimism node
├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy
├── <a href="./technical-documents">technical-documents</a>: audits and post-mortem documents
├── <a href="./teleportr">teleportr</a>: Bridge for teleporting ETH between L1 and L2 at low cost
~~ BEDROCK upgrade - Not production-ready yet, part of next major upgrade ~~
├── <a href="./packages">packages</a>
......
......@@ -61,7 +61,7 @@ def main():
addresses = read_json(addresses_json_path)
else:
log.info('Deploying contracts.')
run_command(['yarn', 'hardhat', '--network', 'devnetL1', 'deploy', '--tags', 'fresh'], env={
run_command(['yarn', 'hardhat', '--network', 'devnetL1', 'deploy'], env={
'CHAIN_ID': '900',
'L1_RPC': 'http://localhost:8545',
'PRIVATE_KEY_DEPLOYER': 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
......@@ -80,8 +80,8 @@ def main():
'CanonicalTransactionChain': '0x0000000000000000000000000000000000000000',
'BondManager': '0x0000000000000000000000000000000000000000',
})
sdk_addresses['L1CrossDomainMessenger'] = addresses['L1CrossDomainMessengerProxy']
sdk_addresses['L1StandardBridge'] = addresses['L1StandardBridgeProxy']
sdk_addresses['L1CrossDomainMessenger'] = addresses['Proxy__OVM_L1CrossDomainMessenger']
sdk_addresses['L1StandardBridge'] = addresses['Proxy__OVM_L1StandardBridge']
sdk_addresses['OptimismPortal'] = addresses['OptimismPortalProxy']
sdk_addresses['L2OutputOracle'] = addresses['L2OutputOracleProxy']
write_json(addresses_json_path, addresses)
......
......@@ -17,7 +17,6 @@ use (
./op-proposer
./op-service
./proxyd
./teleportr
)
replace github.com/ethereum/go-ethereum v1.10.26 => github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be
......
......@@ -86,6 +86,7 @@ github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJ
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
github.com/c-bata/go-prompt v0.2.2 h1:uyKRz6Z6DUyj49QVijyM339UJV9yhbr70gESwbNU3e0=
github.com/casbin/casbin/v2 v2.1.2 h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
......@@ -424,8 +425,6 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82 h1:LneqU9PHDsg/
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537 h1:YGaxtkYjb8mnTvtufv2LKLwCQu2/C7qFB7UtrOlTWOY=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133 h1:JtcyT0rk/9PKOdnKQzuDR+FSjh7SGtJwpgVpfZBRKlQ=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smola/gocompat v0.2.0 h1:6b1oIMlUXIpz//VKEDzPVBK8KG7beVwmHIUEBIs/Pns=
......@@ -606,6 +605,7 @@ google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
......
......@@ -201,11 +201,12 @@ func TestBedrockIndexer(t *testing.T) {
require.NoError(t, err)
proofCl := gethclient.New(rpcClient)
receiptCl := ethclient.NewClient(rpcClient)
wParams, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), proofCl, receiptCl, wdTx.Hash(), finHeader)
wParams, err := withdrawals.ProveWithdrawalParameters(context.Background(), proofCl, receiptCl, wdTx.Hash(), finHeader)
require.NoError(t, err)
l1Opts.Value = big.NewInt(0)
finTx, err := portal.FinalizeWithdrawalTransaction(
// Prove our withdrawal
proveTx, err := portal.ProveWithdrawalTransaction(
l1Opts,
bindings.TypesWithdrawalTransaction{
Nonce: wParams.Nonce,
......@@ -221,6 +222,32 @@ func TestBedrockIndexer(t *testing.T) {
)
require.NoError(t, err)
_, err = e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, time.Minute), l1Client, proveTx.Hash())
require.NoError(t, err)
// Wait for the finalization period to elapse
_, err = withdrawals.WaitForFinalizationPeriod(
e2eutils.TimeoutCtx(t, time.Minute),
l1Client,
predeploys.DevOptimismPortalAddr,
wParams.BlockNumber,
)
require.NoError(t, err)
// Send our finalize withdrawal transaction
finTx, err := portal.FinalizeWithdrawalTransaction(
l1Opts,
bindings.TypesWithdrawalTransaction{
Nonce: wParams.Nonce,
Sender: wParams.Sender,
Target: wParams.Target,
Value: wParams.Value,
GasLimit: wParams.GasLimit,
Data: wParams.Data,
},
)
require.NoError(t, err)
finReceipt, err := e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, time.Minute), l1Client, finTx.Hash())
require.NoError(t, err)
......
......@@ -33,7 +33,7 @@ func main() {
}
ctcAddress := os.Getenv("OVM_CTC_ADDRESS")
if ctcAddress == "" {
log.Error("CTC_ADDRESS environmental variable is required")
log.Error("OVM_CTC_ADDRESS environmental variable is required")
os.Exit(1)
}
sccAddress := os.Getenv("OVM_SCC_ADDRESS")
......
......@@ -147,7 +147,7 @@ func NewBatchSubmitter(cfg Config, l log.Logger) (*BatchSubmitter, error) {
return &BatchSubmitter{
cfg: batcherCfg,
addr: addr,
txMgr: NewTransactionManger(l, txManagerConfig, batchInboxAddress, chainID, sequencerPrivKey, l1Client),
txMgr: NewTransactionManager(l, txManagerConfig, batchInboxAddress, chainID, sequencerPrivKey, l1Client),
done: make(chan struct{}),
log: l,
state: NewChannelManager(l, cfg.ChannelTimeout),
......
......@@ -4,9 +4,9 @@ go 1.18
require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-node v0.10.0
github.com/ethereum-optimism/optimism/op-proposer v0.10.0
github.com/ethereum-optimism/optimism/op-service v0.10.0
github.com/ethereum-optimism/optimism/op-node v0.10.1
github.com/ethereum-optimism/optimism/op-proposer v0.10.1
github.com/ethereum-optimism/optimism/op-service v0.10.1
github.com/ethereum/go-ethereum v1.10.26
github.com/urfave/cli v1.22.9
)
......@@ -23,7 +23,7 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/ethereum-optimism/optimism/op-bindings v0.10.0 // indirect
github.com/ethereum-optimism/optimism/op-bindings v0.10.1 // indirect
github.com/fjl/memsize v0.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
......
......@@ -106,14 +106,14 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be h1:8TdM3M7FjZkrYeGGX9nEVtDDlZ5RiuHtc0mbi5bGKyY=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0 h1:M2nwcOXH9YiRDH6UXnzHI+/eAM5UoFWp7HTlAWrZ4Os=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-node v0.10.0 h1:I6lzMP596P8PfNdPd/z5aO6uWJJABkPKC8jMV3qg9Pc=
github.com/ethereum-optimism/optimism/op-node v0.10.0/go.mod h1:ihuS69UXdeteQENbAZpmEfw4AiVbNrf2ylWRgu1CpEk=
github.com/ethereum-optimism/optimism/op-proposer v0.10.0 h1:G6UpTX20XWGGmPbcMrdjTQqZ6mKj/r3K0u3BaB1jjJU=
github.com/ethereum-optimism/optimism/op-proposer v0.10.0/go.mod h1:6dgDo0DrJnqdMpvi2YFniCCaOa6AzZ1cJfG5jHmYyWQ=
github.com/ethereum-optimism/optimism/op-service v0.10.0 h1:hkWVsVVFf0ybLWwpSqnPasndiFJLk+u++YStaQRRMFY=
github.com/ethereum-optimism/optimism/op-service v0.10.0/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1 h1:OxzYjPmjl5DblgvS0z27M8lZInjdm9Wt6ajozXuMhmw=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-node v0.10.1 h1:kVBaOEOYLV22XEHRhB7dfdmoXepO0kx/RsZQK+Bpk1Y=
github.com/ethereum-optimism/optimism/op-node v0.10.1/go.mod h1:pup7wiiUs9g8cZKwXeB5tEGCqwUUwFVmej9MmSIm6S8=
github.com/ethereum-optimism/optimism/op-proposer v0.10.1 h1:2akYgVF+a7aGRRwXx9x+rdeq1MjRc0+BgZTgS9kGmsE=
github.com/ethereum-optimism/optimism/op-proposer v0.10.1/go.mod h1:dQr8k0SMo48u79Eyt2vn3AuPVtWEgGdz24MavQiz2Cg=
github.com/ethereum-optimism/optimism/op-service v0.10.1 h1:s8CisVat3ia04Z0mW3IiwZ7V1EInyVe3ODq6UXSyJG4=
github.com/ethereum-optimism/optimism/op-service v0.10.1/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
......
......@@ -31,7 +31,7 @@ type TransactionManager struct {
log log.Logger
}
func NewTransactionManger(log log.Logger, txMgrConfg txmgr.Config, batchInboxAddress common.Address, chainID *big.Int, privKey *ecdsa.PrivateKey, l1Client *ethclient.Client) *TransactionManager {
func NewTransactionManager(log log.Logger, txMgrConfg txmgr.Config, batchInboxAddress common.Address, chainID *big.Int, privKey *ecdsa.PrivateKey, l1Client *ethclient.Client) *TransactionManager {
signerFn := func(rawTx types.TxData) (*types.Transaction, error) {
return types.SignNewTx(privKey, types.LatestSignerForChainID(chainID), rawTx)
}
......
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.
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
)
var (
......@@ -19,7 +20,7 @@ var (
// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB.
func MigrateWithdrawals(withdrawals []*LegacyWithdrawal, db vm.StateDB, l1CrossDomainMessenger, l1StandardBridge *common.Address) error {
for _, legacy := range withdrawals {
for i, legacy := range withdrawals {
legacySlot, err := legacy.StorageSlot()
if err != nil {
return err
......@@ -46,6 +47,7 @@ func MigrateWithdrawals(withdrawals []*LegacyWithdrawal, db vm.StateDB, l1CrossD
}
db.SetState(predeploys.L2ToL1MessagePasserAddr, slot, abiTrue)
log.Info("Migrated withdrawal", "number", i, "slot", slot)
}
return nil
}
......
......@@ -218,6 +218,7 @@ func MigrateLegacyETH(db ethdb.Database, addresses []common.Address, allowances
// Set the total supply to 0
stateDB.SetState(predeploys.LegacyERC20ETHAddr, getOVMETHTotalSupplySlot(), common.Hash{})
log.Info("Set the totalSupply to 0")
if !commit {
log.Info("dry run, skipping commit")
......
......@@ -4,9 +4,6 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/ether"
......@@ -17,6 +14,8 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)
......@@ -80,13 +79,18 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
return nil, fmt.Errorf("cannot set implementations: %w", err)
}
log.Info("Starting to migrate withdrawals")
err = crossdomain.MigrateWithdrawals(withdrawals, db, &config.L1CrossDomainMessengerProxy, &config.L1StandardBridgeProxy)
if err != nil {
return nil, fmt.Errorf("cannot migrate withdrawals: %w", err)
}
log.Info("Completed withdrawal migration")
log.Info("Starting to migrate ERC20 ETH")
addrs := migrationData.Addresses()
newRoot, err := ether.MigrateLegacyETH(ldb, addrs, migrationData.OvmAllowances, int(config.L1ChainID), commit)
log.Info("Completed ERC20 ETH migration")
if err != nil {
return nil, fmt.Errorf("cannot migrate legacy eth: %w", err)
}
......@@ -120,6 +124,7 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
}
if !commit {
log.Info("Dry run complete")
return res, nil
}
......
......@@ -103,6 +103,8 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
data, err = l2ooABI.Pack(
"initialize",
config.L2OutputOracleGenesisL2Output,
big.NewInt(0),
uint642Big(uint64(config.L1GenesisBlockTimestamp)),
config.L2OutputOracleProposer,
config.L2OutputOracleOwner,
)
......@@ -275,11 +277,10 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
Name: "L2OutputOracle",
Args: []interface{}{
uint642Big(config.L2OutputOracleSubmissionInterval),
uint642Big(config.L2BlockTime),
[32]byte(config.L2OutputOracleGenesisL2Output),
big.NewInt(0),
big.NewInt(0),
uint642Big(uint64(config.L1GenesisBlockTimestamp)),
uint642Big(config.L2BlockTime),
config.L2OutputOracleProposer,
config.L2OutputOracleOwner,
},
......@@ -338,13 +339,12 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
opts,
backend,
deployment.Args[0].(*big.Int),
deployment.Args[1].([32]byte),
deployment.Args[2].(*big.Int),
deployment.Args[1].(*big.Int),
deployment.Args[2].([32]byte),
deployment.Args[3].(*big.Int),
deployment.Args[4].(*big.Int),
deployment.Args[5].(*big.Int),
deployment.Args[5].(common.Address),
deployment.Args[6].(common.Address),
deployment.Args[7].(common.Address),
)
case "OptimismPortal":
_, tx, _, err = bindings.DeployOptimismPortal(
......
......@@ -55,11 +55,7 @@ func TestBuildL1DeveloperGenesis(t *testing.T) {
require.NoError(t, err)
require.EqualValues(t, config.L2OutputOracleSubmissionInterval, interval.Uint64())
histBlocks, err := oracle.HISTORICALTOTALBLOCKS(callOpts)
require.NoError(t, err)
require.EqualValues(t, 0, histBlocks.Uint64())
startBlock, err := oracle.STARTINGBLOCKNUMBER(callOpts)
startBlock, err := oracle.StartingBlockNumber(callOpts)
require.NoError(t, err)
require.EqualValues(t, 0, startBlock.Uint64())
......
......@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
)
// FundDevAccounts will fund each of the development accounts.
......@@ -51,12 +52,14 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int
// the proxy admin address. LegacyERC20ETH lives in the
// 0xDead namespace so it can be ignored here
if addr == predeploys.GovernanceTokenAddr || addr == predeploys.ProxyAdminAddr {
log.Info("Skipping setting proxy", "address", addr)
continue
}
db.CreateAccount(addr)
db.SetCode(addr, depBytecode)
db.SetState(addr, AdminSlot, proxyAdminAddr.Hash())
log.Trace("Set proxy", "address", addr, "admin", proxyAdminAddr)
}
return nil
}
......@@ -96,17 +99,20 @@ func SetImplementations(db vm.StateDB, storage state.StorageConfig, immutable im
// Use the genrated bytecode when there are immutables
// otherwise use the artifact deployed bytecode
if bytecode, ok := deployResults[name]; ok {
log.Info("Setting deployed bytecode with immutables", "name", name, "address", addr)
db.SetCode(addr, bytecode)
} else {
depBytecode, err := bindings.GetDeployedBytecode(name)
if err != nil {
return err
}
log.Info("Setting deployed bytecode from solc compiler output", "name", name, "address", addr)
db.SetCode(addr, depBytecode)
}
// Set the storage values
if storageConfig, ok := storage[name]; ok {
log.Info("Setting storage", "name", name, "address", *address)
if err := state.SetStorage(name, *address, storageConfig, db); err != nil {
return err
}
......
......@@ -3,7 +3,7 @@ module github.com/ethereum-optimism/optimism/op-chain-ops
go 1.18
require (
github.com/ethereum-optimism/optimism/op-bindings v0.10.0
github.com/ethereum-optimism/optimism/op-bindings v0.10.1
github.com/ethereum/go-ethereum v1.10.26
github.com/holiman/uint256 v1.2.0
github.com/mattn/go-isatty v0.0.14
......
......@@ -76,8 +76,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be h1:8TdM3M7FjZkrYeGGX9nEVtDDlZ5RiuHtc0mbi5bGKyY=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0 h1:M2nwcOXH9YiRDH6UXnzHI+/eAM5UoFWp7HTlAWrZ4Os=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1 h1:OxzYjPmjl5DblgvS0z27M8lZInjdm9Wt6ajozXuMhmw=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
......
......@@ -379,14 +379,68 @@ func (s *CrossLayerUser) Address() common.Address {
return s.L1.address
}
// ActCompleteWithdrawal creates a L1 withdrawal completion tx for latest withdrawal.
// ActCompleteWithdrawal creates a L1 proveWithdrawal tx for latest withdrawal.
// The tx hash is remembered as the last L1 tx, to check as L1 actor.
func (s *CrossLayerUser) ActProveWithdrawal(t Testing) {
s.L1.lastTxHash = s.ProveWithdrawal(t, s.lastL2WithdrawalTxHash)
}
// ProveWithdrawal creates a L1 proveWithdrawal tx for the given L2 withdrawal tx, returning the tx hash.
func (s *CrossLayerUser) ProveWithdrawal(t Testing, l2TxHash common.Hash) common.Hash {
// Figure out when our withdrawal was included
receipt := s.L2.CheckReceipt(t, true, l2TxHash)
l2WithdrawalBlock, err := s.L2.env.EthCl.BlockByNumber(t.Ctx(), receipt.BlockNumber)
require.NoError(t, err)
// Figure out what the Output oracle on L1 has seen so far
l2OutputBlockNr, err := s.L1.env.Bindings.L2OutputOracle.LatestBlockNumber(&bind.CallOpts{})
require.NoError(t, err)
l2OutputBlock, err := s.L2.env.EthCl.BlockByNumber(t.Ctx(), l2OutputBlockNr)
require.NoError(t, err)
// Check if the L2 output is even old enough to include the withdrawal
if l2OutputBlock.NumberU64() < l2WithdrawalBlock.NumberU64() {
t.InvalidAction("the latest L2 output is %d and is not past L2 block %d that includes the withdrawal yet, no withdrawal can be proved yet", l2OutputBlock.NumberU64(), l2WithdrawalBlock.NumberU64())
return common.Hash{}
}
// We generate a proof for the latest L2 output, which shouldn't require archive-node data if it's recent enough.
header, err := s.L2.env.EthCl.HeaderByNumber(t.Ctx(), l2OutputBlockNr)
require.NoError(t, err)
params, err := withdrawals.ProveWithdrawalParameters(t.Ctx(), s.L2.env.Bindings.ProofClient, s.L2.env.EthCl, s.lastL2WithdrawalTxHash, header)
require.NoError(t, err)
// Create the prove tx
tx, err := s.L1.env.Bindings.OptimismPortal.ProveWithdrawalTransaction(
&s.L1.txOpts,
bindings.TypesWithdrawalTransaction{
Nonce: params.Nonce,
Sender: params.Sender,
Target: params.Target,
Value: params.Value,
GasLimit: params.GasLimit,
Data: params.Data,
},
params.BlockNumber,
params.OutputRootProof,
params.WithdrawalProof,
)
require.NoError(t, err)
// Send the actual tx (since tx opts don't send by default)
err = s.L1.env.EthCl.SendTransaction(t.Ctx(), tx)
require.NoError(t, err, "must send prove tx")
return tx.Hash()
}
// ActCompleteWithdrawal creates a L1 withdrawal finalization tx for latest withdrawal.
// The tx hash is remembered as the last L1 tx, to check as L1 actor.
// The withdrawal functions like CompleteWithdrawal
func (s *CrossLayerUser) ActCompleteWithdrawal(t Testing) {
s.L1.lastTxHash = s.CompleteWithdrawal(t, s.lastL2WithdrawalTxHash)
}
// CompleteWithdrawal creates a L1 withdrawal completion tx for the given L2 withdrawal tx, returning the tx hash.
// CompleteWithdrawal creates a L1 withdrawal finalization tx for the given L2 withdrawal tx, returning the tx hash.
// It's an invalid action to attempt to complete a withdrawal that has not passed the L1 finalization period yet
func (s *CrossLayerUser) CompleteWithdrawal(t Testing, l2TxHash common.Hash) common.Hash {
finalizationPeriod, err := s.L1.env.Bindings.OptimismPortal.FINALIZATIONPERIODSECONDS(&bind.CallOpts{})
......@@ -420,9 +474,11 @@ func (s *CrossLayerUser) CompleteWithdrawal(t Testing, l2TxHash common.Hash) com
}
// We generate a proof for the latest L2 output, which shouldn't require archive-node data if it's recent enough.
// Note that for the `FinalizeWithdrawalTransaction` function, this proof isn't needed. We simply use some of the
// params for the `WithdrawalTransaction` type generated in the bindings.
header, err := s.L2.env.EthCl.HeaderByNumber(t.Ctx(), l2OutputBlockNr)
require.NoError(t, err)
params, err := withdrawals.FinalizeWithdrawalParameters(t.Ctx(), s.L2.env.Bindings.ProofClient, s.L2.env.EthCl, s.lastL2WithdrawalTxHash, header)
params, err := withdrawals.ProveWithdrawalParameters(t.Ctx(), s.L2.env.Bindings.ProofClient, s.L2.env.EthCl, s.lastL2WithdrawalTxHash, header)
require.NoError(t, err)
// Create the withdrawal tx
......@@ -436,14 +492,11 @@ func (s *CrossLayerUser) CompleteWithdrawal(t Testing, l2TxHash common.Hash) com
GasLimit: params.GasLimit,
Data: params.Data,
},
params.BlockNumber,
params.OutputRootProof,
params.WithdrawalProof,
)
require.NoError(t, err)
// Send the actual tx (since tx opts don't send by default)
err = s.L1.env.EthCl.SendTransaction(t.Ctx(), tx)
require.NoError(t, err, "must send tx")
require.NoError(t, err, "must send finalize tx")
return tx.Hash()
}
......@@ -17,6 +17,8 @@ import (
// - transact on L2
// - deposit on L1
// - withdraw from L2
// - prove tx on L1
// - wait 1 week + 1 second
// - finalize withdrawal on L1
func TestCrossLayerUser(gt *testing.T) {
t := NewDefaultTesting(gt)
......@@ -133,7 +135,22 @@ func TestCrossLayerUser(gt *testing.T) {
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "proposal failed")
}
// make the L1 side of the withdrawal tx
// prove our withdrawal on L1
alice.ActProveWithdrawal(t)
// include proved withdrawal in new L1 block
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(alice.Address())(t)
miner.ActL1EndBlock(t)
// check withdrawal succeeded
alice.L1.ActCheckReceiptStatusOfLastTx(true)(t)
// A bit hacky- Mines an empty block with the time delta
// of the finalization period (12s) + 1 in order for the
// withdrawal to be finalized successfully.
miner.ActL1StartBlock(13)(t)
miner.ActL1EndBlock(t)
// make the L1 finalize withdrawal tx
alice.ActCompleteWithdrawal(t)
// include completed withdrawal in new L1 block
miner.ActL1StartBlock(12)(t)
......
......@@ -85,6 +85,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
L1GenesisBlockGasUsed: 0,
L1GenesisBlockParentHash: common.Hash{},
L1GenesisBlockBaseFeePerGas: uint64ToBig(1000_000_000), // 1 gwei
FinalizationPeriodSeconds: 12,
L2GenesisBlockNonce: 0,
L2GenesisBlockExtraData: []byte{},
......
......@@ -8,12 +8,12 @@ require (
github.com/docker/docker v20.10.21+incompatible
github.com/docker/go-connections v0.4.0
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-batcher v0.10.0
github.com/ethereum-optimism/optimism/op-bindings v0.10.0
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.0
github.com/ethereum-optimism/optimism/op-node v0.10.0
github.com/ethereum-optimism/optimism/op-proposer v0.10.0
github.com/ethereum-optimism/optimism/op-service v0.10.0
github.com/ethereum-optimism/optimism/op-batcher v0.10.1
github.com/ethereum-optimism/optimism/op-bindings v0.10.1
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.1
github.com/ethereum-optimism/optimism/op-node v0.10.1
github.com/ethereum-optimism/optimism/op-proposer v0.10.1
github.com/ethereum-optimism/optimism/op-service v0.10.1
github.com/ethereum/go-ethereum v1.10.26
github.com/libp2p/go-libp2p v0.23.3
github.com/stretchr/testify v1.8.0
......
......@@ -159,16 +159,18 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be h1:8TdM3M7FjZkrYeGGX9nEVtDDlZ5RiuHtc0mbi5bGKyY=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y=
github.com/ethereum-optimism/optimism/op-batcher v0.10.0 h1:mI3udjjhK7FRoYnr7PNRSRPNldm7Vn1X1Kpl2Mixpkg=
github.com/ethereum-optimism/optimism/op-batcher v0.10.0/go.mod h1:u14Dchn2F0sdWxH/V6wtNVFCQJAfrURb/mwvYP/RiMk=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0 h1:M2nwcOXH9YiRDH6UXnzHI+/eAM5UoFWp7HTlAWrZ4Os=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-node v0.10.0 h1:I6lzMP596P8PfNdPd/z5aO6uWJJABkPKC8jMV3qg9Pc=
github.com/ethereum-optimism/optimism/op-node v0.10.0/go.mod h1:ihuS69UXdeteQENbAZpmEfw4AiVbNrf2ylWRgu1CpEk=
github.com/ethereum-optimism/optimism/op-proposer v0.10.0 h1:G6UpTX20XWGGmPbcMrdjTQqZ6mKj/r3K0u3BaB1jjJU=
github.com/ethereum-optimism/optimism/op-proposer v0.10.0/go.mod h1:6dgDo0DrJnqdMpvi2YFniCCaOa6AzZ1cJfG5jHmYyWQ=
github.com/ethereum-optimism/optimism/op-service v0.10.0 h1:hkWVsVVFf0ybLWwpSqnPasndiFJLk+u++YStaQRRMFY=
github.com/ethereum-optimism/optimism/op-service v0.10.0/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/ethereum-optimism/optimism/op-batcher v0.10.1 h1:y/PlBJfmJ74UzbPPLntIfCO/l4yAOeCPPyUsiZd7C9Q=
github.com/ethereum-optimism/optimism/op-batcher v0.10.1/go.mod h1:GUWRM7Bhc05H4gUj8ggTZnegVS4bfltZq0gqXh18Fvk=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1 h1:OxzYjPmjl5DblgvS0z27M8lZInjdm9Wt6ajozXuMhmw=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.1 h1:SJls5Zs8mXooHItwCFsxCyOhssuGIJCnucjGJjo+eQ0=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.1/go.mod h1:b3nDzF9sIqDUVRM58P9d1u7+bWPOEMX3gqyCuL0CMjI=
github.com/ethereum-optimism/optimism/op-node v0.10.1 h1:kVBaOEOYLV22XEHRhB7dfdmoXepO0kx/RsZQK+Bpk1Y=
github.com/ethereum-optimism/optimism/op-node v0.10.1/go.mod h1:pup7wiiUs9g8cZKwXeB5tEGCqwUUwFVmej9MmSIm6S8=
github.com/ethereum-optimism/optimism/op-proposer v0.10.1 h1:2akYgVF+a7aGRRwXx9x+rdeq1MjRc0+BgZTgS9kGmsE=
github.com/ethereum-optimism/optimism/op-proposer v0.10.1/go.mod h1:dQr8k0SMo48u79Eyt2vn3AuPVtWEgGdz24MavQiz2Cg=
github.com/ethereum-optimism/optimism/op-service v0.10.1 h1:s8CisVat3ia04Z0mW3IiwZ7V1EInyVe3ODq6UXSyJG4=
github.com/ethereum-optimism/optimism/op-service v0.10.1/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
......
......@@ -160,11 +160,6 @@ func writeDefaultJWT(t *testing.T) string {
return jwtPath
}
type L2OOContractConfig struct {
SubmissionFrequency *big.Int
HistoricalTotalBlocks *big.Int
}
type DepositContractConfig struct {
L2Oracle common.Address
FinalizationPeriod *big.Int
......
......@@ -279,11 +279,13 @@ func TestConfirmationDepth(t *testing.T) {
l2VerHead, err := l2Verif.BlockByNumber(ctx, nil)
require.NoError(t, err)
info, err := derive.L1InfoDepositTxData(l2SeqHead.Transactions()[0].Data())
seqInfo, err := derive.L1InfoDepositTxData(l2SeqHead.Transactions()[0].Data())
require.NoError(t, err)
require.LessOrEqual(t, info.Number+seqConfDepth, l1Head.NumberU64(), "the L2 head block should have an origin older than the L1 head block by at least the sequencer conf depth")
require.LessOrEqual(t, seqInfo.Number+seqConfDepth, l1Head.NumberU64(), "the seq L2 head block should have an origin older than the L1 head block by at least the sequencer conf depth")
require.LessOrEqual(t, l2VerHead.Time()+cfg.DeployConfig.L1BlockTime*verConfDepth, l2SeqHead.Time(), "the L2 verifier head should lag behind the sequencer without delay by at least the verifier conf depth")
verInfo, err := derive.L1InfoDepositTxData(l2VerHead.Transactions()[0].Data())
require.NoError(t, err)
require.LessOrEqual(t, verInfo.Number+verConfDepth, l1Head.NumberU64(), "the ver L2 head block should have an origin older than the L1 head block by at least the verifier conf depth")
}
// TestFinalize tests if L2 finalizes after sufficient time after L1 finalizes
......@@ -817,7 +819,7 @@ func TestWithdrawals(t *testing.T) {
startBalance, err = l1Client.BalanceAt(ctx, fromAddr, nil)
require.Nil(t, err)
// Wait for finalization and then create the Finalized Withdrawal Transaction
// Get l2BlockNumber for proof generation
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel()
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, receipt.BlockNumber)
......@@ -834,14 +836,16 @@ func TestWithdrawals(t *testing.T) {
receiptCl := ethclient.NewClient(rpcClient)
// Now create withdrawal
params, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), proofCl, receiptCl, tx.Hash(), header)
params, err := withdrawals.ProveWithdrawalParameters(context.Background(), proofCl, receiptCl, tx.Hash(), header)
require.Nil(t, err)
portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client)
require.Nil(t, err)
opts.Value = nil
tx, err = portal.FinalizeWithdrawalTransaction(
// Prove withdrawal
tx, err = portal.ProveWithdrawalTransaction(
opts,
bindings.TypesWithdrawalTransaction{
Nonce: params.Nonce,
......@@ -855,17 +859,42 @@ func TestWithdrawals(t *testing.T) {
params.OutputRootProof,
params.WithdrawalProof,
)
require.Nil(t, err)
// Ensure that our withdrawal was proved successfully
proveReceipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "prove withdrawal")
require.Equal(t, types.ReceiptStatusSuccessful, proveReceipt.Status)
// Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel()
_, err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, params.BlockNumber)
require.Nil(t, err)
// Finalize withdrawal
tx, err = portal.FinalizeWithdrawalTransaction(
opts,
bindings.TypesWithdrawalTransaction{
Nonce: params.Nonce,
Sender: params.Sender,
Target: params.Target,
Value: params.Value,
GasLimit: params.GasLimit,
Data: params.Data,
},
)
require.Nil(t, err)
receipt, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
// Ensure that our withdrawal was finalized successfully
finalizeReceipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "finalize withdrawal")
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
require.Equal(t, types.ReceiptStatusSuccessful, finalizeReceipt.Status)
// Verify balance after withdrawal
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
header, err = l1Client.HeaderByNumber(ctx, receipt.BlockNumber)
header, err = l1Client.HeaderByNumber(ctx, finalizeReceipt.BlockNumber)
require.Nil(t, err)
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
......@@ -875,8 +904,9 @@ func TestWithdrawals(t *testing.T) {
// Ensure that withdrawal - gas fees are added to the L1 balance
// Fun fact, the fee is greater than the withdrawal amount
// NOTE: The gas fees include *both* the ProveWithdrawalTransaction and FinalizeWithdrawalTransaction transactions.
diff = new(big.Int).Sub(endBalance, startBalance)
fees = calcGasFees(receipt.GasUsed, tx.GasTipCap(), tx.GasFeeCap(), header.BaseFee)
fees = calcGasFees(proveReceipt.GasUsed+finalizeReceipt.GasUsed, tx.GasTipCap(), tx.GasFeeCap(), header.BaseFee)
withdrawAmount = withdrawAmount.Sub(withdrawAmount, fees)
require.Equal(t, withdrawAmount, diff)
}
......
......@@ -29,6 +29,13 @@ fuzz:
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzUnmarshallLogEvent ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzParseFrames ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFrameUnmarshalBinary ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzBatchRoundTrip ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDeriveDepositsRoundTrip ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDeriveDepositsBadVersion ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzParseL1InfoDepositTxDataValid ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzParseL1InfoDepositTxDataBadLength ./rollup/derive
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzRejectCreateBlockBadTimestamp ./rollup/driver
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDecodeDepositTxDataToL1Info ./rollup/driver
.PHONY: \
......
......@@ -6,12 +6,13 @@ require (
github.com/btcsuite/btcd v0.23.3
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0
github.com/ethereum-optimism/optimism/op-bindings v0.10.0
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.0
github.com/ethereum-optimism/optimism/op-service v0.10.0
github.com/ethereum-optimism/optimism/op-bindings v0.10.1
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.1
github.com/ethereum-optimism/optimism/op-service v0.10.1
github.com/ethereum/go-ethereum v1.10.26
github.com/golang/snappy v0.0.4
github.com/google/go-cmp v0.5.8
github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/holiman/uint256 v1.2.0
......
......@@ -145,12 +145,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be h1:8TdM3M7FjZkrYeGGX9nEVtDDlZ5RiuHtc0mbi5bGKyY=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0 h1:M2nwcOXH9YiRDH6UXnzHI+/eAM5UoFWp7HTlAWrZ4Os=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.0 h1:WwxEw+w7FF3aqqnZoN4ipFK+O1bVrf3/qN4jcm15ek0=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.0/go.mod h1:TYuGpQKaWofyb3ZotSJjmluGgH0c3gNO40de3C9ueks=
github.com/ethereum-optimism/optimism/op-service v0.10.0 h1:hkWVsVVFf0ybLWwpSqnPasndiFJLk+u++YStaQRRMFY=
github.com/ethereum-optimism/optimism/op-service v0.10.0/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1 h1:OxzYjPmjl5DblgvS0z27M8lZInjdm9Wt6ajozXuMhmw=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.1 h1:SJls5Zs8mXooHItwCFsxCyOhssuGIJCnucjGJjo+eQ0=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.1/go.mod h1:b3nDzF9sIqDUVRM58P9d1u7+bWPOEMX3gqyCuL0CMjI=
github.com/ethereum-optimism/optimism/op-service v0.10.1 h1:s8CisVat3ia04Z0mW3IiwZ7V1EInyVe3ODq6UXSyJG4=
github.com/ethereum-optimism/optimism/op-service v0.10.1/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
......@@ -250,6 +250,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 h1:Ep/joEub9YwcjRY6ND3+Y/w0ncE540RtGatVhtZL0/Q=
github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
......
package derive
import (
"testing"
"github.com/ethereum-optimism/optimism/op-node/testutils/fuzzerutils"
fuzz "github.com/google/gofuzz"
"github.com/stretchr/testify/require"
)
// FuzzBatchRoundTrip executes a fuzz test similar to TestBatchRoundTrip, which tests that arbitrary BatchData will be
// encoded and decoded without loss of its original values.
func FuzzBatchRoundTrip(f *testing.F) {
f.Fuzz(func(t *testing.T, fuzzedData []byte) {
// Create our fuzzer wrapper to generate complex values
typeProvider := fuzz.NewFromGoFuzz(fuzzedData).NilChance(0).MaxDepth(10000).NumElements(0, 0x100).AllowUnexportedFields(true)
fuzzerutils.AddFuzzerFunctions(typeProvider)
// Create our batch data from fuzzed data
var batchData BatchData
typeProvider.Fuzz(&batchData)
// Encode our batch data
enc, err := batchData.MarshalBinary()
require.NoError(t, err)
// Decode our encoded batch data
var dec BatchData
err = dec.UnmarshalBinary(enc)
require.NoError(t, err)
// Ensure the round trip encoding of batch data did not result in data loss
require.Equal(t, &batchData, &dec, "round trip batch encoding/decoding did not match original values")
})
}
package derive
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum-optimism/optimism/op-node/testutils/fuzzerutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
fuzz "github.com/google/gofuzz"
"github.com/stretchr/testify/require"
)
// fuzzReceipts is similar to makeReceipts except it uses the fuzzer to populate DepositTx fields.
func fuzzReceipts(typeProvider *fuzz.Fuzzer, blockHash common.Hash, depositContractAddr common.Address) (receipts []*types.Receipt, expectedDeposits []*types.DepositTx) {
// Determine how many receipts to generate (capped)
var receiptCount uint64
typeProvider.Fuzz(&receiptCount)
// Cap our receipt count otherwise we might generate for too long and our fuzzer will assume we hung
if receiptCount > 0x10 {
receiptCount = 0x10
}
// Create every receipt we intend to
logIndex := uint(0)
for i := uint64(0); i < receiptCount; i++ {
// Obtain our fuzz parameters for generating this receipt
var txReceiptValues struct {
GoodReceipt bool
DepositLogs []bool
}
typeProvider.Fuzz(&txReceiptValues)
// Generate a list of transaction receipts
var logs []*types.Log
status := types.ReceiptStatusSuccessful
if txReceiptValues.GoodReceipt {
status = types.ReceiptStatusFailed
}
// Determine if this log will be a deposit log or not and generate it accordingly
for _, isDeposit := range txReceiptValues.DepositLogs {
var ev *types.Log
var err error
if isDeposit {
// Generate a user deposit source
source := UserDepositSource{L1BlockHash: blockHash, LogIndex: uint64(logIndex)}
// Fuzz parameters to construct our deposit log
var fuzzedDepositInfo struct {
FromAddr *common.Address
ToAddr *common.Address
Value *big.Int
Gas uint64
Data []byte
Mint *big.Int
}
typeProvider.Fuzz(&fuzzedDepositInfo)
// Create our deposit transaction
dep := &types.DepositTx{
SourceHash: source.SourceHash(),
From: *fuzzedDepositInfo.FromAddr,
To: fuzzedDepositInfo.ToAddr,
Value: fuzzedDepositInfo.Value,
Gas: fuzzedDepositInfo.Gas,
Data: fuzzedDepositInfo.Data,
Mint: fuzzedDepositInfo.Mint,
IsSystemTransaction: false,
}
// Marshal our actual log event
ev, err = MarshalDepositLogEvent(depositContractAddr, dep)
if err != nil {
panic(err)
}
// If we have a good version and our tx succeeded, we add this to our list of expected deposits to
// return.
if status == types.ReceiptStatusSuccessful {
expectedDeposits = append(expectedDeposits, dep)
}
} else {
// If we're generated an unrelated log event (not deposit), fuzz some random parameters to use.
var randomUnrelatedLogInfo struct {
Addr *common.Address
Topics []common.Hash
Data []byte
}
typeProvider.Fuzz(&randomUnrelatedLogInfo)
// Generate the random log
ev = testutils.GenerateLog(*randomUnrelatedLogInfo.Addr, randomUnrelatedLogInfo.Topics, randomUnrelatedLogInfo.Data)
}
ev.TxIndex = uint(i)
ev.Index = logIndex
ev.BlockHash = blockHash
logs = append(logs, ev)
logIndex++
}
// Add our receipt to our list
receipts = append(receipts, &types.Receipt{
Type: types.DynamicFeeTxType,
Status: status,
Logs: logs,
BlockHash: blockHash,
TransactionIndex: uint(i),
})
}
return
}
// FuzzDeriveDepositsRoundTrip tests the derivation of deposits from transaction receipt event logs. It mixes
// valid and invalid deposit transactions and ensures all valid deposits are derived as expected.
// This is a fuzz test corresponding to TestDeriveUserDeposits.
func FuzzDeriveDepositsRoundTrip(f *testing.F) {
f.Fuzz(func(t *testing.T, fuzzedData []byte) {
// Create our fuzzer wrapper to generate complex values
typeProvider := fuzz.NewFromGoFuzz(fuzzedData).NilChance(0).MaxDepth(10000).NumElements(0, 0x100).Funcs(
func(e *big.Int, c fuzz.Continue) {
var temp [32]byte
c.Fuzz(&temp)
e.SetBytes(temp[:])
},
func(e *common.Hash, c fuzz.Continue) {
var temp [32]byte
c.Fuzz(&temp)
e.SetBytes(temp[:])
},
func(e *common.Address, c fuzz.Continue) {
var temp [20]byte
c.Fuzz(&temp)
e.SetBytes(temp[:])
})
// Create a dummy block hash for this block
var blockHash common.Hash
typeProvider.Fuzz(&blockHash)
// Fuzz to generate some random deposit events
receipts, expectedDeposits := fuzzReceipts(typeProvider, blockHash, MockDepositContractAddr)
// Derive our user deposits from the transaction receipts
derivedDeposits, err := UserDeposits(receipts, MockDepositContractAddr)
require.NoError(t, err)
// Ensure all deposits we derived matched what we expected to receive.
require.Equal(t, len(derivedDeposits), len(expectedDeposits))
for i, derivedDeposit := range derivedDeposits {
expectedDeposit := expectedDeposits[i]
require.Equal(t, expectedDeposit, derivedDeposit)
}
})
}
// FuzzDeriveDepositsBadVersion ensures that if a deposit transaction receipt event log specifies an invalid deposit
// version, no deposits should be derived.
func FuzzDeriveDepositsBadVersion(f *testing.F) {
f.Fuzz(func(t *testing.T, fuzzedData []byte) {
// Create our fuzzer wrapper to generate complex values
typeProvider := fuzz.NewFromGoFuzz(fuzzedData).NilChance(0).MaxDepth(10000).NumElements(0, 0x100)
fuzzerutils.AddFuzzerFunctions(typeProvider)
// Create a dummy block hash for this block
var blockHash common.Hash
typeProvider.Fuzz(&blockHash)
// Fuzz to generate some random deposit events
receipts, _ := fuzzReceipts(typeProvider, blockHash, MockDepositContractAddr)
// Loop through all receipt logs and let the fuzzer determine which (if any) to patch.
hasBadDepositVersion := false
for _, receipt := range receipts {
// TODO: Using a hardcoded index (Topics[3]) here is not ideal. The MarshalDepositLogEvent method should
// be spliced apart to be more configurable for these tests.
// Loop for each log in this receipt and check if it has a deposit event from our contract
for _, log := range receipt.Logs {
if log.Address == MockDepositContractAddr && len(log.Topics) >= 4 && log.Topics[0] == DepositEventABIHash {
// Determine if we should set a bad deposit version for this log
var patchBadDeposit bool
typeProvider.Fuzz(&patchBadDeposit)
if patchBadDeposit {
// Generate any topic but the deposit event versions we support.
// TODO: As opposed to keeping this hardcoded, a method such as IsValidVersion(v) should be
// used here.
badTopic := DepositEventVersion0
for badTopic == DepositEventVersion0 {
typeProvider.Fuzz(&badTopic)
}
// Set our bad topic and update our state
log.Topics[3] = badTopic
hasBadDepositVersion = true
}
}
}
}
// Derive our user deposits from the transaction receipts
_, err := UserDeposits(receipts, MockDepositContractAddr)
// If we patched a bad deposit version this iteration, we should expect an error and not be able to proceed
// further
if hasBadDepositVersion {
require.Errorf(t, err, "")
return
}
require.NoError(t, err, "")
})
}
package derive
import (
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum-optimism/optimism/op-node/testutils/fuzzerutils"
fuzz "github.com/google/gofuzz"
"github.com/stretchr/testify/require"
)
// FuzzParseL1InfoDepositTxDataValid is a fuzz test built from TestParseL1InfoDepositTxData, which constructs random
// L1 deposit tx info and derives a tx from it, then derives the info back from the tx, to ensure round-trip
// derivation is upheld. This generates "valid" data and ensures it is always derived back to original values.
func FuzzParseL1InfoDepositTxDataValid(f *testing.F) {
f.Fuzz(func(t *testing.T, fuzzedData []byte) {
// Create our fuzzer wrapper to generate complex values
typeProvider := fuzz.NewFromGoFuzz(fuzzedData).NilChance(0).MaxDepth(10000).NumElements(0, 0x100)
fuzzerutils.AddFuzzerFunctions(typeProvider)
var l1Info testutils.MockBlockInfo
typeProvider.Fuzz(&l1Info)
var seqNr uint64
typeProvider.Fuzz(&seqNr)
var sysCfg eth.SystemConfig
typeProvider.Fuzz(&sysCfg)
// Create our deposit tx from our info
depTx, err := L1InfoDeposit(seqNr, &l1Info, sysCfg)
require.NoError(t, err, "error creating deposit tx from L1 info")
// Get our info from out deposit tx
res, err := L1InfoDepositTxData(depTx.Data)
require.NoError(t, err, "expected valid deposit info")
// Verify all parameters match in our round trip deriving operations
require.Equal(t, res.Number, l1Info.NumberU64())
require.Equal(t, res.Time, l1Info.Time())
require.True(t, res.BaseFee.Sign() >= 0)
require.Equal(t, res.BaseFee.Bytes(), l1Info.BaseFee().Bytes())
require.Equal(t, res.BlockHash, l1Info.Hash())
require.Equal(t, res.SequenceNumber, seqNr)
require.Equal(t, res.BatcherAddr, sysCfg.BatcherAddr)
require.Equal(t, res.L1FeeOverhead, sysCfg.Overhead)
require.Equal(t, res.L1FeeScalar, sysCfg.Scalar)
})
}
// Reverse of the above test. Accepts a random byte string and attempts to extract L1Info from it,
// then attempts to convert that info back into the tx data and compare it with the original input.
func FuzzDecodeDepositTxDataToL1Info(f *testing.F) {
f.Fuzz(func(t *testing.T, fuzzedData []byte) {
// Get our info from out deposit tx
res, err := L1InfoDepositTxData(fuzzedData)
if err != nil {
return
}
l1Info := testutils.MockBlockInfo{
InfoHash: res.BlockHash,
InfoNum: res.Number,
InfoTime: res.Time,
InfoBaseFee: res.BaseFee,
}
sysCfg := eth.SystemConfig{
BatcherAddr: res.BatcherAddr,
Overhead: res.L1FeeOverhead,
Scalar: res.L1FeeScalar,
GasLimit: uint64(0),
}
depTx, err := L1InfoDeposit(res.SequenceNumber, &l1Info, sysCfg)
require.NoError(t, err, "error creating deposit tx from L1 info")
require.Equal(t, depTx.Data, fuzzedData)
})
}
// FuzzParseL1InfoDepositTxDataBadLength is a fuzz test built from TestParseL1InfoDepositTxData, which constructs
// random L1 deposit tx info and derives a tx from it, then derives the info back from the tx, to ensure round-trip
// derivation is upheld. This generates "invalid" data and ensures it always throws an error where expected.
func FuzzParseL1InfoDepositTxDataBadLength(f *testing.F) {
const expectedDepositTxDataLength = 4 + 32 + 32 + 32 + 32 + 32
f.Fuzz(func(t *testing.T, fuzzedData []byte) {
// Derive a transaction from random fuzzed data
_, err := L1InfoDepositTxData(fuzzedData)
// If the data is null, or too short or too long, we expect an error
if fuzzedData == nil || len(fuzzedData) != expectedDepositTxDataLength {
require.Error(t, err)
}
})
}
// On develop
package driver
import (
"context"
"errors"
"math/rand"
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
type TestDummyOutputImpl struct {
willError bool
SequencerIface
}
func (d TestDummyOutputImpl) CreateNewBlock(ctx context.Context, l2Head eth.L2BlockRef, l2SafeHead eth.BlockID, l2Finalized eth.BlockID, l1Origin eth.L1BlockRef) (eth.L2BlockRef, *eth.ExecutionPayload, error) {
// If we're meant to error, return one
if d.willError {
return l2Head, nil, errors.New("the TestDummyOutputImpl.createNewBlock operation failed")
}
payload := eth.ExecutionPayload{
ParentHash: common.Hash{},
FeeRecipient: common.Address{},
StateRoot: eth.Bytes32{},
ReceiptsRoot: eth.Bytes32{},
LogsBloom: eth.Bytes256{},
PrevRandao: eth.Bytes32{},
BlockNumber: 0,
GasLimit: 0,
GasUsed: 0,
Timestamp: 0,
ExtraData: nil,
BaseFeePerGas: eth.Uint256Quantity{},
BlockHash: common.Hash{},
Transactions: []eth.Data{},
}
return l2Head, &payload, nil
}
type TestDummyDerivationPipeline struct {
DerivationPipeline
l2Head eth.L2BlockRef
l2SafeHead eth.L2BlockRef
l2Finalized eth.L2BlockRef
}
func (d TestDummyDerivationPipeline) Reset() {}
func (d TestDummyDerivationPipeline) Step(ctx context.Context) error { return nil }
func (d TestDummyDerivationPipeline) SetUnsafeHead(head eth.L2BlockRef) {}
func (d TestDummyDerivationPipeline) AddUnsafePayload(payload *eth.ExecutionPayload) {}
func (d TestDummyDerivationPipeline) Finalized() eth.L2BlockRef { return d.l2Head }
func (d TestDummyDerivationPipeline) SafeL2Head() eth.L2BlockRef { return d.l2SafeHead }
func (d TestDummyDerivationPipeline) UnsafeL2Head() eth.L2BlockRef { return d.l2Finalized }
type TestDummyL1OriginSelector struct {
retval eth.L1BlockRef
}
func (l TestDummyL1OriginSelector) FindL1Origin(ctx context.Context, l1Head eth.L1BlockRef, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) {
return l.retval, nil
}
// TestRejectCreateBlockBadTimestamp tests that a block creation with invalid timestamps will be caught.
// This does not test:
// - The findL1Origin call (it is hardcoded to be the head)
// - The outputInterface used to create a new block from a given payload.
// - The DerivationPipeline setting unsafe head (a mock provider is used to pretend to set it)
// - Metrics (only mocked enough to let the method proceed)
// - Publishing (network is set to nil so publishing won't occur)
func TestRejectCreateBlockBadTimestamp(t *testing.T) {
// Create our random provider
rng := rand.New(rand.NewSource(rand.Int63()))
// Create our context for methods to execute under
ctx := context.Background()
// Create our fake L1/L2 heads and link them accordingly
l1HeadRef := testutils.RandomBlockRef(rng)
l2HeadRef := testutils.RandomL2BlockRef(rng)
l2l1OriginBlock := l1HeadRef
l2HeadRef.L1Origin = l2l1OriginBlock.ID()
// Create a rollup config
cfg := rollup.Config{
BlockTime: uint64(60),
Genesis: rollup.Genesis{
L1: l1HeadRef.ID(),
L2: l2HeadRef.ID(),
L2Time: 0x7000, // dummy value
},
}
// Patch our timestamp so we fail
l2HeadRef.Time = l2l1OriginBlock.Time - (cfg.BlockTime * 2)
// Create our outputter
outputProvider := TestDummyOutputImpl{willError: false}
// Create our state
s := Driver{
l1State: &L1State{
l1Head: l1HeadRef,
log: log.New(),
metrics: &metrics.Metrics{TransactionsSequencedTotal: prometheus.NewCounter(prometheus.CounterOpts{})},
},
log: log.New(),
l1OriginSelector: TestDummyL1OriginSelector{retval: l1HeadRef},
config: &cfg,
sequencer: outputProvider,
derivation: TestDummyDerivationPipeline{},
metrics: &metrics.Metrics{TransactionsSequencedTotal: prometheus.NewCounter(prometheus.CounterOpts{})},
}
// Create a new block
// - L2Head's L1Origin, its timestamp should be greater than L1 genesis.
// - L2Head timestamp + BlockTime should be greater than or equal to the L1 Time.
err := s.createNewL2Block(ctx)
// Verify the L1Origin's block number is greater than L1 genesis in our config.
if l2l1OriginBlock.Number < s.config.Genesis.L1.Number {
require.NoError(t, err, "L1Origin block number should be greater than the L1 genesis block number")
}
// Verify the new L2 block to create will have a time stamp equal or newer than our L1 origin block we derive from.
if l2HeadRef.Time+cfg.BlockTime < l2l1OriginBlock.Time {
// If not, we expect a specific error.
// TODO: This isn't the cleanest, we should construct + compare the whole error message.
require.NotNil(t, err)
require.Contains(t, err.Error(), "cannot build L2 block on top")
require.Contains(t, err.Error(), "for time")
require.Contains(t, err.Error(), "before L1 origin")
return
}
// If we expected the outputter to error, capture that here
if outputProvider.willError {
require.NotNil(t, err, "outputInterface failed to createNewBlock, so createNewL2Block should also have failed")
return
}
// Otherwise we should have no error.
require.NoError(t, err, "error raised in TestRejectCreateBlockBadTimestamp")
}
// FuzzRejectCreateBlockBadTimestamp is a property test derived from the TestRejectCreateBlockBadTimestamp unit test.
// It fuzzes timestamps and block times to find a configuration to violate error checking.
func FuzzRejectCreateBlockBadTimestamp(f *testing.F) {
f.Fuzz(func(t *testing.T, randSeed int64, l2Time uint64, blockTime uint64, forceOutputFail bool, currentL2HeadTime uint64) {
// Create our random provider
rng := rand.New(rand.NewSource(randSeed))
// Create our context for methods to execute under
ctx := context.Background()
// Create our fake L1/L2 heads and link them accordingly
l1HeadRef := testutils.RandomBlockRef(rng)
l2HeadRef := testutils.RandomL2BlockRef(rng)
l2l1OriginBlock := l1HeadRef
l2HeadRef.L1Origin = l2l1OriginBlock.ID()
// TODO: Cap our block time so it doesn't overflow
if blockTime > 0x100000 {
blockTime = 0x100000
}
// Create a rollup config
cfg := rollup.Config{
BlockTime: blockTime,
Genesis: rollup.Genesis{
L1: l1HeadRef.ID(),
L2: l2HeadRef.ID(),
L2Time: l2Time, // dummy value
},
}
// Patch our timestamp so we fail
l2HeadRef.Time = currentL2HeadTime
// Create our outputter
outputProvider := TestDummyOutputImpl{willError: forceOutputFail}
// Create our state
s := Driver{
l1State: &L1State{
l1Head: l1HeadRef,
log: log.New(),
metrics: &metrics.Metrics{TransactionsSequencedTotal: prometheus.NewCounter(prometheus.CounterOpts{})},
},
log: log.New(),
l1OriginSelector: TestDummyL1OriginSelector{retval: l1HeadRef},
config: &cfg,
sequencer: outputProvider,
derivation: TestDummyDerivationPipeline{},
metrics: &metrics.Metrics{TransactionsSequencedTotal: prometheus.NewCounter(prometheus.CounterOpts{})},
}
// Create a new block
// - L2Head's L1Origin, its timestamp should be greater than L1 genesis.
// - L2Head timestamp + BlockTime should be greater than or equal to the L1 Time.
err := s.createNewL2Block(ctx)
// Verify the L1Origin's timestamp is greater than L1 genesis in our config.
if l2l1OriginBlock.Number < s.config.Genesis.L1.Number {
require.NoError(t, err)
return
}
// Verify the new L2 block to create will have a time stamp equal or newer than our L1 origin block we derive from.
if l2HeadRef.Time+cfg.BlockTime < l2l1OriginBlock.Time {
// If not, we expect a specific error.
// TODO: This isn't the cleanest, we should construct + compare the whole error message.
require.NotNil(t, err)
require.Contains(t, err.Error(), "cannot build L2 block on top")
require.Contains(t, err.Error(), "for time")
require.Contains(t, err.Error(), "before L1 origin")
return
}
// Otherwise we should have no error.
require.Nil(t, err)
// If we expected the outputter to error, capture that here
if outputProvider.willError {
require.NotNil(t, err, "outputInterface failed to createNewBlock, so createNewL2Block should also have failed")
return
}
// Otherwise we should have no error.
require.NoError(t, err, "L1Origin block number should be greater than the L1 genesis block number")
})
}
package fuzzerutils
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
fuzz "github.com/google/gofuzz"
)
// AddFuzzerFunctions takes a fuzz.Fuzzer and adds a list of functions to handle different
// data types in a fuzzing campaign. It adds support for commonly used types throughout the
// application.
func AddFuzzerFunctions(fuzzer *fuzz.Fuzzer) {
fuzzer.Funcs(
func(e *big.Int, c fuzz.Continue) {
var temp [32]byte
c.Fuzz(&temp)
e.SetBytes(temp[:])
},
func(e *common.Hash, c fuzz.Continue) {
var temp [32]byte
c.Fuzz(&temp)
e.SetBytes(temp[:])
},
func(e *common.Address, c fuzz.Continue) {
var temp [20]byte
c.Fuzz(&temp)
e.SetBytes(temp[:])
},
)
}
......@@ -129,8 +129,9 @@ type ReceiptClient interface {
TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error)
}
// FinalizedWithdrawalParameters is the set of parameters to pass to the FinalizedWithdrawal function
type FinalizedWithdrawalParameters struct {
// ProvenWithdrawalParameters is the set of parameters to pass to the ProveWithdrawalTransaction
// and FinalizeWithdrawalTransaction functions
type ProvenWithdrawalParameters struct {
Nonce *big.Int
Sender common.Address
Target common.Address
......@@ -142,40 +143,40 @@ type FinalizedWithdrawalParameters struct {
WithdrawalProof [][]byte // List of trie nodes to prove L2 storage
}
// FinalizeWithdrawalParameters queries L2 to generate all withdrawal parameters and proof necessary to finalize an withdrawal on L1.
// ProveWithdrawalParameters queries L2 to generate all withdrawal parameters and proof necessary to prove a withdrawal on L1.
// The header provided is very important. It should be a block (timestamp) for which there is a submitted output in the L2 Output Oracle
// contract. If not, the withdrawal will fail as it the storage proof cannot be verified if there is no submitted state root.
func FinalizeWithdrawalParameters(ctx context.Context, proofCl ProofClient, l2ReceiptCl ReceiptClient, txHash common.Hash, header *types.Header) (FinalizedWithdrawalParameters, error) {
func ProveWithdrawalParameters(ctx context.Context, proofCl ProofClient, l2ReceiptCl ReceiptClient, txHash common.Hash, header *types.Header) (ProvenWithdrawalParameters, error) {
// Transaction receipt
receipt, err := l2ReceiptCl.TransactionReceipt(ctx, txHash)
if err != nil {
return FinalizedWithdrawalParameters{}, err
return ProvenWithdrawalParameters{}, err
}
// Parse the receipt
ev, err := ParseMessagePassed(receipt)
if err != nil {
return FinalizedWithdrawalParameters{}, err
return ProvenWithdrawalParameters{}, err
}
// Generate then verify the withdrawal proof
withdrawalHash, err := WithdrawalHash(ev)
if !bytes.Equal(withdrawalHash[:], ev.WithdrawalHash[:]) {
return FinalizedWithdrawalParameters{}, errors.New("Computed withdrawal hash incorrectly")
return ProvenWithdrawalParameters{}, errors.New("Computed withdrawal hash incorrectly")
}
if err != nil {
return FinalizedWithdrawalParameters{}, err
return ProvenWithdrawalParameters{}, err
}
slot := StorageSlotOfWithdrawalHash(withdrawalHash)
p, err := proofCl.GetProof(ctx, predeploys.L2ToL1MessagePasserAddr, []string{slot.String()}, header.Number)
if err != nil {
return FinalizedWithdrawalParameters{}, err
return ProvenWithdrawalParameters{}, err
}
// TODO: Could skip this step, but it's nice to double check it
err = VerifyProof(header.Root, p)
if err != nil {
return FinalizedWithdrawalParameters{}, err
return ProvenWithdrawalParameters{}, err
}
if len(p.StorageProof) != 1 {
return FinalizedWithdrawalParameters{}, errors.New("invalid amount of storage proofs")
return ProvenWithdrawalParameters{}, errors.New("invalid amount of storage proofs")
}
// Encode it as expected by the contract
......@@ -184,7 +185,7 @@ func FinalizeWithdrawalParameters(ctx context.Context, proofCl ProofClient, l2Re
trieNodes[i] = common.FromHex(s)
}
return FinalizedWithdrawalParameters{
return ProvenWithdrawalParameters{
Nonce: ev.Nonce,
Sender: ev.Sender,
Target: ev.Target,
......
......@@ -4,9 +4,9 @@ go 1.18
require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-bindings v0.10.0
github.com/ethereum-optimism/optimism/op-node v0.10.0
github.com/ethereum-optimism/optimism/op-service v0.10.0
github.com/ethereum-optimism/optimism/op-bindings v0.10.1
github.com/ethereum-optimism/optimism/op-node v0.10.1
github.com/ethereum-optimism/optimism/op-service v0.10.1
github.com/ethereum/go-ethereum v1.10.26
github.com/stretchr/testify v1.8.0
github.com/urfave/cli v1.22.9
......
......@@ -107,12 +107,12 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be h1:8TdM3M7FjZkrYeGGX9nEVtDDlZ5RiuHtc0mbi5bGKyY=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0 h1:M2nwcOXH9YiRDH6UXnzHI+/eAM5UoFWp7HTlAWrZ4Os=
github.com/ethereum-optimism/optimism/op-bindings v0.10.0/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-node v0.10.0 h1:I6lzMP596P8PfNdPd/z5aO6uWJJABkPKC8jMV3qg9Pc=
github.com/ethereum-optimism/optimism/op-node v0.10.0/go.mod h1:ihuS69UXdeteQENbAZpmEfw4AiVbNrf2ylWRgu1CpEk=
github.com/ethereum-optimism/optimism/op-service v0.10.0 h1:hkWVsVVFf0ybLWwpSqnPasndiFJLk+u++YStaQRRMFY=
github.com/ethereum-optimism/optimism/op-service v0.10.0/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1 h1:OxzYjPmjl5DblgvS0z27M8lZInjdm9Wt6ajozXuMhmw=
github.com/ethereum-optimism/optimism/op-bindings v0.10.1/go.mod h1:UeTZlpZyhOL3y9Sogzvbn8Z3q1tDmZEv1VmGxMiZYCg=
github.com/ethereum-optimism/optimism/op-node v0.10.1 h1:kVBaOEOYLV22XEHRhB7dfdmoXepO0kx/RsZQK+Bpk1Y=
github.com/ethereum-optimism/optimism/op-node v0.10.1/go.mod h1:pup7wiiUs9g8cZKwXeB5tEGCqwUUwFVmej9MmSIm6S8=
github.com/ethereum-optimism/optimism/op-service v0.10.1 h1:s8CisVat3ia04Z0mW3IiwZ7V1EInyVe3ODq6UXSyJG4=
github.com/ethereum-optimism/optimism/op-service v0.10.1/go.mod h1:d7IryyBivUjxhQt91nNBxUainSFmRjwf6H/nCNBowvk=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
......
......@@ -26,6 +26,8 @@ RUN source $HOME/.profile && \
FROM ethereum/client-go:alltools-v1.10.25 as geth
FROM ghcr.io/crytic/echidna/echidna:testing-master as echidna-test
FROM python:3.8.13-slim-bullseye
ENV GOPATH=/go
......@@ -36,6 +38,7 @@ COPY --from=foundry-build /opt/foundry/target/release/forge /usr/local/bin/forge
COPY --from=foundry-build /opt/foundry/target/release/cast /usr/local/bin/cast
COPY --from=foundry-build /opt/foundry/target/release/anvil /usr/local/bin/anvil
COPY --from=geth /usr/local/bin/abigen /usr/local/bin/abigen
COPY --from=echidna-test /usr/local/bin/echidna-test /usr/local/bin/echidna-test
COPY check-changed.sh /usr/local/bin/check-changed
RUN apt-get update && \
......
......@@ -20,7 +20,6 @@
"ops/docker/ci-builder",
"ops/docker/foundry",
"proxyd",
"teleportr",
"endpoint-monitor"
],
"nohoist": [
......
This diff is collapsed.
This diff is collapsed.
......@@ -19,6 +19,15 @@ import { Semver } from "../universal/Semver.sol";
* Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
*/
contract OptimismPortal is Initializable, ResourceMetering, Semver {
/**
* @notice Represents a proven withdrawal
*/
struct ProvenWithdrawal {
bytes32 outputRoot;
uint128 timestamp;
uint128 l2BlockNumber;
}
/**
* @notice Version of the deposit event.
*/
......@@ -61,6 +70,11 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
*/
mapping(bytes32 => bool) public finalizedWithdrawals;
/**
* @notice A mapping of withdrawal hashes to `ProvenWithdrawal` data.
*/
mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;
/**
* @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.
......@@ -77,6 +91,17 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
bytes opaqueData
);
/**
* @notice Emitted when a withdrawal transaction is proven.
*
* @param withdrawalHash Hash of the withdrawal transaction.
*/
event WithdrawalProven(
bytes32 indexed withdrawalHash,
address indexed from,
address indexed to
);
/**
* @notice Emitted when a withdrawal transaction is finalized.
*
......@@ -125,47 +150,38 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
}
/**
* @notice Finalizes a withdrawal transaction.
* @notice Proves a withdrawal transaction.
*
* @param _tx Withdrawal transaction to finalize.
* @param _l2BlockNumber L2 block number of the outputRoot.
* @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root.
* @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract.
*/
function finalizeWithdrawalTransaction(
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _l2BlockNumber,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
) external {
// Prevent nested withdrawals within withdrawals.
require(
l2Sender == DEFAULT_L2_SENDER,
"OptimismPortal: can only trigger one withdrawal per transaction"
);
// Prevent users from creating a deposit transaction where this address is the message
// sender on L2.
// In the context of the proxy delegate calling to this implementation,
// address(this) will return the address of the proxy.
//
// Because this is checked here, we do not need to check again in
// `finalizeWithdrawalTransaction`
require(
_tx.target != address(this),
"OptimismPortal: you cannot send messages to the portal contract"
);
// Get the output root. This will fail if there is no
// output root for the given block number.
Types.OutputProposal memory proposal = L2_ORACLE.getL2Output(_l2BlockNumber);
// Ensure that enough time has passed since the proposal was submitted before allowing a
// withdrawal. Under the assumption that the fault proof mechanism is operating correctly,
// we can infer that any withdrawal that has passed the finalization period must be valid
// and can therefore be operated on.
require(_isOutputFinalized(proposal), "OptimismPortal: proposal is not yet finalized");
// Get the output root and load onto the stack to prevent multiple mloads. This will
// fail if there is no output root for the given block number.
bytes32 outputRoot = L2_ORACLE.getL2Output(_l2BlockNumber).outputRoot;
// Verify that the output root can be generated with the elements in the proof.
require(
proposal.outputRoot == Hashing.hashOutputRootProof(_outputRootProof),
outputRoot == Hashing.hashOutputRootProof(_outputRootProof),
"OptimismPortal: invalid output root proof"
);
......@@ -173,8 +189,19 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
// and to prevent replay attacks.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
// Load the ProvenWithdrawal into memory
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// Only allow re-proving a withdrawal transaction if the output root has changed.
require(
provenWithdrawal.timestamp == 0 ||
(_l2BlockNumber == provenWithdrawal.l2BlockNumber &&
outputRoot != provenWithdrawal.outputRoot),
"OptimismPortal: withdrawal hash has already been proven"
);
// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract on
// L2. If this is true, then we know that this withdrawal was actually triggered on L2
// L2. If this is true, then we know that this withdrawal was actually triggered on L2
// and can therefore be relayed on L1.
require(
_verifyWithdrawalInclusion(
......@@ -185,6 +212,73 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
"OptimismPortal: invalid withdrawal inclusion proof"
);
// Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`,
// and `l2BlockNumber` in the `provenWithdrawals` mapping. A withdrawalHash can only
// be proven once to prevent a censorship attack unless it is submitted again
// with a different outputRoot.
provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
outputRoot: outputRoot,
timestamp: uint128(block.timestamp),
l2BlockNumber: uint128(_l2BlockNumber)
});
// Emit a `WithdrawalProven` event.
emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
}
/**
* @notice Finalizes a withdrawal transaction.
*
* @param _tx Withdrawal transaction to finalize.
*/
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external {
// Prevent nested withdrawals within withdrawals.
require(
l2Sender == DEFAULT_L2_SENDER,
"OptimismPortal: can only trigger one withdrawal per transaction"
);
// All withdrawals have a unique hash, we'll use this as the identifier for the withdrawal
// and to prevent replay attacks.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
// Grab the proven withdrawal from the `provenWithdrawals` map.
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// Ensure that the withdrawal has been proven
require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven");
// Ensure that the proven withdrawal's timestamp is greater than the
// L2 Oracle's starting timestamp.
require(
provenWithdrawal.timestamp >= L2_ORACLE.startingTimestamp(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
);
// Ensure that the withdrawal's finalization period has elapsed.
require(
_isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
"OptimismPortal: proven withdrawal finalization period has not elapsed"
);
// Grab the OutputProposal from the L2 Oracle
Types.OutputProposal memory proposal = L2_ORACLE.getL2Output(
provenWithdrawal.l2BlockNumber
);
// Check that the output proposal hasn't been updated.
require(
proposal.outputRoot == provenWithdrawal.outputRoot,
"OptimismPortal: output root proven is not the same as current output root"
);
// Perform second checks on the withdrawal's finalization period, this time with
// the `OutputProposal`'s timestamp fetched from the L2 Oracle.
require(
_isFinalizationPeriodElapsed(proposal.timestamp),
"OptimismPortal: output proposal finalization period has not elapsed"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
require(
finalizedWithdrawals[withdrawalHash] == false,
......@@ -225,8 +319,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
* @param _l2BlockNumber The number of the L2 block.
*/
function isBlockFinalized(uint256 _l2BlockNumber) external view returns (bool) {
Types.OutputProposal memory proposal = L2_ORACLE.getL2Output(_l2BlockNumber);
return _isOutputFinalized(proposal);
return _isFinalizationPeriodElapsed(L2_ORACLE.getL2Output(_l2BlockNumber).timestamp);
}
/**
......@@ -277,16 +370,13 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
}
/**
* @notice Determine if an L2 Output is finalized.
* @notice Determine if the finalization period has elapsed with respect to the
* passed timestamp.
*
* @param _proposal The output proposal to check.
* @param _timestamp The timestamp to check.
*/
function _isOutputFinalized(Types.OutputProposal memory _proposal)
internal
view
returns (bool)
{
return block.timestamp > _proposal.timestamp + FINALIZATION_PERIOD_SECONDS;
function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) {
return block.timestamp > _timestamp + FINALIZATION_PERIOD_SECONDS;
}
/**
......
......@@ -56,14 +56,22 @@ contract BaseSystemDictator is Ownable {
}
/**
* @notice Set of implementation addresses.
* @notice Fixed L2OutputOracle config.
*/
struct L2OutputOracleConfig {
bytes32 l2OutputOracleGenesisL2Output;
address l2OutputOracleProposer;
address l2OutputOracleOwner;
}
/**
* @notice Dynamic L2OutputOracle config.
*/
struct L2OutputOracleDynamicConfig {
bytes32 l2OutputOracleStartingL2Output;
uint256 l2OutputOracleStartingBlockNumber;
uint256 l2OutputOracleStartingTimestamp;
}
/**
* @notice Values for the system config contract.
*/
......@@ -91,11 +99,21 @@ contract BaseSystemDictator is Ownable {
*/
DeployConfig public config;
/**
* @notice Dynamic configuration for the L2OutputOracle.
*/
L2OutputOracleDynamicConfig public l2OutputOracleDynamicConfig;
/**
* @notice Current step;
*/
uint8 public currentStep = 1;
/**
* @notice Whether or not dynamic config has been set.
*/
bool public dynamicConfigSet;
/**
* @notice Checks that the current step is the expected step, then bumps the current step.
*
......@@ -114,4 +132,16 @@ contract BaseSystemDictator is Ownable {
config = _config;
_transferOwnership(config.globalConfig.controller);
}
/**
* @notice Allows the owner to update dynamic L2OutputOracle config.
*
* @param _l2OutputOracleDynamicConfig Dynamic L2OutputOracle config.
*/
function updateL2OutputOracleDynamicConfig(
L2OutputOracleDynamicConfig memory _l2OutputOracleDynamicConfig
) external onlyOwner {
l2OutputOracleDynamicConfig = _l2OutputOracleDynamicConfig;
dynamicConfigSet = true;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { L2OutputOracle } from "../L1/L2OutputOracle.sol";
import { OptimismPortal } from "../L1/OptimismPortal.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { L1CrossDomainMessenger } from "../L1/L1CrossDomainMessenger.sol";
import { BaseSystemDictator } from "./BaseSystemDictator.sol";
/**
* @title FreshSystemDictator
* @notice The FreshSystemDictator is responsible for coordinating initialization of a fresh
* deployment of the Optimism system. We expect that all proxies and implementations
* already be deployed before this contract is used.
*/
contract FreshSystemDictator is BaseSystemDictator {
/**
* @param _config System configuration.
*/
constructor(DeployConfig memory _config) BaseSystemDictator(_config) {}
/**
* @notice Upgrades and initializes proxy contracts.
*/
function step1() external onlyOwner step(1) {
// Upgrade and initialize the L2OutputOracle.
config.globalConfig.proxyAdmin.upgradeAndCall(
payable(config.proxyAddressConfig.l2OutputOracleProxy),
address(config.implementationAddressConfig.l2OutputOracleImpl),
abi.encodeCall(
L2OutputOracle.initialize,
(
config.l2OutputOracleConfig.l2OutputOracleGenesisL2Output,
config.l2OutputOracleConfig.l2OutputOracleProposer,
config.l2OutputOracleConfig.l2OutputOracleOwner
)
)
);
// Upgrade and initialize the OptimismPortal.
config.globalConfig.proxyAdmin.upgradeAndCall(
payable(config.proxyAddressConfig.optimismPortalProxy),
address(config.implementationAddressConfig.optimismPortalImpl),
abi.encodeCall(OptimismPortal.initialize, ())
);
// Upgrade and initialize the L1CrossDomainMessenger.
config.globalConfig.proxyAdmin.upgradeAndCall(
payable(config.proxyAddressConfig.l1CrossDomainMessengerProxy),
address(config.implementationAddressConfig.l1CrossDomainMessengerImpl),
abi.encodeCall(L1CrossDomainMessenger.initialize, (config.globalConfig.finalOwner))
);
// Upgrade the L1StandardBridge (no initializer).
config.globalConfig.proxyAdmin.upgrade(
payable(config.proxyAddressConfig.l1StandardBridgeProxy),
address(config.implementationAddressConfig.l1StandardBridgeImpl)
);
// Upgrade the OptimismMintableERC20Factory (no initializer).
config.globalConfig.proxyAdmin.upgrade(
payable(config.proxyAddressConfig.optimismMintableERC20FactoryProxy),
address(config.implementationAddressConfig.optimismMintableERC20FactoryImpl)
);
// Upgrade the L1ERC721Bridge (no initializer).
config.globalConfig.proxyAdmin.upgrade(
payable(config.proxyAddressConfig.l1ERC721BridgeProxy),
address(config.implementationAddressConfig.l1ERC721BridgeImpl)
);
// Upgrade and initialize the SystemConfig.
config.globalConfig.proxyAdmin.upgradeAndCall(
payable(config.proxyAddressConfig.systemConfigProxy),
address(config.implementationAddressConfig.systemConfigImpl),
abi.encodeCall(
SystemConfig.initialize,
(
config.systemConfigConfig.owner,
config.systemConfigConfig.overhead,
config.systemConfigConfig.scalar,
config.systemConfigConfig.batcherHash,
config.systemConfigConfig.gasLimit
)
)
);
}
/**
* @notice Transfers ownership to final owner.
*/
function step2() external onlyOwner step(2) {
// Transfer ownership of the ProxyAdmin to the final owner.
config.globalConfig.proxyAdmin.transferOwnership(config.globalConfig.finalOwner);
}
}
......@@ -17,6 +17,26 @@ import { BaseSystemDictator } from "./BaseSystemDictator.sol";
* proxies and implementations already be deployed before this contract is used.
*/
contract MigrationSystemDictator is BaseSystemDictator {
/**
* @notice Step after which exit 1 can no longer be used.
*/
uint8 public constant EXIT_1_NO_RETURN_STEP = 3;
/**
* @notice Step where proxy ownership is transferred.
*/
uint8 public constant PROXY_TRANSFER_STEP = 4;
/**
* @notice Whether or not the deployment is finalized.
*/
bool public finalized;
/**
* @notice Address of the old L1CrossDomainMessenger implementation.
*/
address public oldL1CrossDomainMessenger;
/**
* @param _config System configuration.
*/
......@@ -49,17 +69,37 @@ contract MigrationSystemDictator is BaseSystemDictator {
}
/**
* @notice Pauses the system by shutting down the L1CrossDomainMessenger and clearing many
* addresses inside the AddressManager.
* @notice Pauses the system by shutting down the L1CrossDomainMessenger and setting the
* deposit halt flag to tell the Sequencer's DTL to stop accepting deposits.
*/
function step2() external onlyOwner step(2) {
// Pause the L1CrossDomainMessenger
L1CrossDomainMessenger(config.proxyAddressConfig.l1CrossDomainMessengerProxy).pause();
// Store the address of the old L1CrossDomainMessenger implementation. We will need this
// address in the case that we have to exit early.
oldL1CrossDomainMessenger = config.globalConfig.addressManager.getAddress(
"OVM_L1CrossDomainMessenger"
);
// Temporarily brick the L1CrossDomainMessenger by setting its implementation address to
// address(0) which will cause the ResolvedDelegateProxy to revert. Better than pausing
// the L1CrossDomainMessenger via pause() because it can be easily reverted.
config.globalConfig.addressManager.setAddress("OVM_L1CrossDomainMessenger", address(0));
// Remove all dead addresses from the AddressManager
string[18] memory deads = [
"Proxy__OVM_L1CrossDomainMessenger",
"Proxy__OVM_L1StandardBridge",
// Set the DTL shutoff block, which will tell the DTL to stop syncing new deposits from the
// CanonicalTransactionChain. We do this by setting an address in the AddressManager
// because the DTL already has a reference to the AddressManager and this way we don't also
// need to give it a reference to the SystemDictator.
config.globalConfig.addressManager.setAddress(
"DTL_SHUTOFF_BLOCK",
address(uint160(block.number))
);
}
/**
* @notice Removes deprecated addresses from the AddressManager.
*/
function step3() external onlyOwner step(EXIT_1_NO_RETURN_STEP) {
// Remove all deprecated addresses from the AddressManager
string[17] memory deprecated = [
"OVM_CanonicalTransactionChain",
"OVM_L2CrossDomainMessenger",
"OVM_DecompressionPrecompileAddress",
......@@ -75,18 +115,19 @@ contract MigrationSystemDictator is BaseSystemDictator {
"OVM_StateManagerFactory",
"OVM_StateTransitionerFactory",
"OVM_SafetyChecker",
"OVM_L1MultiMessageRelayer"
"OVM_L1MultiMessageRelayer",
"BondManager"
];
for (uint256 i = 0; i < deads.length; i++) {
config.globalConfig.addressManager.setAddress(deads[i], address(0));
for (uint256 i = 0; i < deprecated.length; i++) {
config.globalConfig.addressManager.setAddress(deprecated[i], address(0));
}
}
/**
* @notice Transfers system ownership to the ProxyAdmin.
*/
function step3() external onlyOwner step(3) {
function step4() external onlyOwner step(PROXY_TRANSFER_STEP) {
// Transfer ownership of the AddressManager to the ProxyAdmin.
config.globalConfig.addressManager.transferOwnership(
address(config.globalConfig.proxyAdmin)
......@@ -101,7 +142,13 @@ contract MigrationSystemDictator is BaseSystemDictator {
/**
* @notice Upgrades and initializes proxy contracts.
*/
function step4() external onlyOwner step(4) {
function step5() external onlyOwner step(5) {
// Dynamic config must be set before we can initialize the L2OutputOracle.
require(
dynamicConfigSet,
"MigrationSystemDictator: dynamic oracle config is not yet initialized"
);
// Upgrade and initialize the L2OutputOracle.
config.globalConfig.proxyAdmin.upgradeAndCall(
payable(config.proxyAddressConfig.l2OutputOracleProxy),
......@@ -109,7 +156,9 @@ contract MigrationSystemDictator is BaseSystemDictator {
abi.encodeCall(
L2OutputOracle.initialize,
(
config.l2OutputOracleConfig.l2OutputOracleGenesisL2Output,
l2OutputOracleDynamicConfig.l2OutputOracleStartingL2Output,
l2OutputOracleDynamicConfig.l2OutputOracleStartingBlockNumber,
l2OutputOracleDynamicConfig.l2OutputOracleStartingTimestamp,
config.l2OutputOracleConfig.l2OutputOracleProposer,
config.l2OutputOracleConfig.l2OutputOracleOwner
)
......@@ -123,13 +172,34 @@ contract MigrationSystemDictator is BaseSystemDictator {
abi.encodeCall(OptimismPortal.initialize, ())
);
// Upgrade the L1CrossDomainMessenger. No initializer because this is
// already initialized.
// Upgrade the L1CrossDomainMessenger.
config.globalConfig.proxyAdmin.upgrade(
payable(config.proxyAddressConfig.l1CrossDomainMessengerProxy),
address(config.implementationAddressConfig.l1CrossDomainMessengerImpl)
);
// Try to initialize the L1CrossDomainMessenger, only fail if it's already been initialized.
try
L1CrossDomainMessenger(config.proxyAddressConfig.l1CrossDomainMessengerProxy)
.initialize(address(this))
{
// L1CrossDomainMessenger is the one annoying edge case difference between existing
// networks and fresh networks because in existing networks it'll already be
// initialized but in fresh networks it won't be. Try/catch is the easiest and most
// consistent way to handle this because initialized() is not exposed publicly.
} catch Error(string memory reason) {
require(
keccak256(abi.encodePacked(reason)) ==
keccak256("Initializable: contract is already initialized"),
string.concat(
"MigrationSystemDictator: unexpected error initializing L1XDM: ",
reason
)
);
} catch {
revert("MigrationSystemDictator: unexpected error initializing L1XDM (no reason)");
}
// Transfer ETH from the L1StandardBridge to the OptimismPortal.
config.globalConfig.proxyAdmin.upgradeAndCall(
payable(config.proxyAddressConfig.l1StandardBridgeProxy),
......@@ -170,12 +240,15 @@ contract MigrationSystemDictator is BaseSystemDictator {
)
)
);
// Pause the L1CrossDomainMessenger, chance to check that everything is OK.
L1CrossDomainMessenger(config.proxyAddressConfig.l1CrossDomainMessengerProxy).pause();
}
/**
* @notice Unpauses the system at which point the system should be fully operational.
*/
function step5() external onlyOwner step(5) {
function step6() external onlyOwner step(6) {
// Unpause the L1CrossDomainMessenger.
L1CrossDomainMessenger(config.proxyAddressConfig.l1CrossDomainMessengerProxy).unpause();
}
......@@ -183,12 +256,47 @@ contract MigrationSystemDictator is BaseSystemDictator {
/**
* @notice Tranfers admin ownership to the final owner.
*/
function step6() external onlyOwner step(6) {
function finalize() external onlyOwner {
// Transfer ownership of the L1CrossDomainMessenger to the final owner.
L1CrossDomainMessenger(config.proxyAddressConfig.l1CrossDomainMessengerProxy)
.transferOwnership(config.globalConfig.finalOwner);
// Transfer ownership of the ProxyAdmin to the final owner.
config.globalConfig.proxyAdmin.transferOwnership(config.globalConfig.finalOwner);
// Optionally also transfer AddressManager and L1StandardBridge if we still own it. Might
// happen if we're exiting early.
if (currentStep <= PROXY_TRANSFER_STEP) {
// Transfer ownership of the AddressManager to the final owner.
config.globalConfig.addressManager.transferOwnership(
address(config.globalConfig.finalOwner)
);
// Transfer ownership of the L1StandardBridge to the final owner.
L1ChugSplashProxy(payable(config.proxyAddressConfig.l1StandardBridgeProxy)).setOwner(
address(config.globalConfig.finalOwner)
);
}
finalized = true;
}
/**
* @notice First exit point, can only be called before step 3 is executed.
*/
function exit1() external onlyOwner {
require(
currentStep == EXIT_1_NO_RETURN_STEP,
"MigrationSystemDictator: can only exit1 before step 3 is executed"
);
// Reset the L1CrossDomainMessenger to the old implementation.
config.globalConfig.addressManager.setAddress(
"OVM_L1CrossDomainMessenger",
oldL1CrossDomainMessenger
);
// Unset the DTL shutoff block which will allow the DTL to sync again.
config.globalConfig.addressManager.setAddress("DTL_SHUTOFF_BLOCK", address(0));
}
}
......@@ -2,7 +2,7 @@
pragma solidity 0.8.15;
/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { Test, StdUtils } from "forge-std/Test.sol";
import { L2OutputOracle } from "../L1/L2OutputOracle.sol";
import { L2ToL1MessagePasser } from "../L2/L2ToL1MessagePasser.sol";
import { L1StandardBridge } from "../L1/L1StandardBridge.sol";
......@@ -98,7 +98,6 @@ contract L2OutputOracle_Initializer is CommonTest {
uint256 internal submissionInterval = 1800;
uint256 internal l2BlockTime = 2;
bytes32 internal genesisL2Output = keccak256(abi.encode(0));
uint256 internal historicalTotalBlocks = 199;
uint256 internal startingBlockNumber = 200;
uint256 internal startingTimestamp = 1000;
......@@ -121,11 +120,10 @@ contract L2OutputOracle_Initializer is CommonTest {
// Deploy the L2OutputOracle and transfer owernship to the proposer
oracleImpl = new L2OutputOracle(
submissionInterval,
l2BlockTime,
genesisL2Output,
historicalTotalBlocks,
startingBlockNumber,
startingTimestamp,
l2BlockTime,
proposer,
owner
);
......@@ -133,7 +131,10 @@ contract L2OutputOracle_Initializer is CommonTest {
vm.prank(multisig);
proxy.upgradeToAndCall(
address(oracleImpl),
abi.encodeCall(L2OutputOracle.initialize, (genesisL2Output, proposer, owner))
abi.encodeCall(
L2OutputOracle.initialize,
(genesisL2Output, startingBlockNumber, startingTimestamp, proposer, owner)
)
);
oracle = L2OutputOracle(address(proxy));
vm.label(address(oracle), "L2OutputOracle");
......@@ -453,7 +454,7 @@ contract ERC721Bridge_Initializer is Messenger_Initializer {
}
contract FFIInterface is Test {
function getFinalizeWithdrawalTransactionInputs(Types.WithdrawalTransaction memory _tx)
function getProveWithdrawalTransactionInputs(Types.WithdrawalTransaction memory _tx)
external
returns (
bytes32,
......@@ -466,7 +467,7 @@ contract FFIInterface is Test {
string[] memory cmds = new string[](9);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "getFinalizeWithdrawalTransactionInputs";
cmds[2] = "getProveWithdrawalTransactionInputs";
cmds[3] = vm.toString(_tx.nonce);
cmds[4] = vm.toString(_tx.sender);
cmds[5] = vm.toString(_tx.target);
......
......@@ -4,7 +4,7 @@ pragma solidity 0.8.15;
import { CommonTest, Portal_Initializer } from "./CommonTest.t.sol";
import { CrossDomainOwnable } from "../L2/CrossDomainOwnable.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
import { Vm } from "forge-std/Vm.sol";
import { Vm, VmSafe } from "forge-std/Vm.sol";
import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
contract XDomainSetter is CrossDomainOwnable {
......@@ -62,11 +62,11 @@ contract CrossDomainOwnableThroughPortal_Test is Portal_Initializer {
// Simulate the operation of the `op-node` by parsing data
// from logs
Vm.Log[] memory logs = vm.getRecordedLogs();
VmSafe.Log[] memory logs = vm.getRecordedLogs();
// Only 1 log emitted
assertEq(logs.length, 1);
Vm.Log memory log = logs[0];
VmSafe.Log memory log = logs[0];
// It is the expected topic
bytes32 topic = log.topics[0];
......
......@@ -21,19 +21,20 @@ contract Hashing_Test is CommonTest {
}
function test_hashCrossDomainMessage_differential(
uint256 _nonce,
uint240 _nonce,
uint16 _version,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) external {
// Discard any fuzz tests with an invalid version
(, uint16 version) = Encoding.decodeVersionedNonce(_nonce);
vm.assume(version < 2);
// Ensure the version is valid
uint16 version = uint16(bound(uint256(_version), 0, 1));
uint256 nonce = Encoding.encodeVersionedNonce(_nonce, version);
bytes32 _hash = ffi.hashCrossDomainMessage(
_nonce,
nonce,
_sender,
_target,
_value,
......@@ -42,7 +43,7 @@ contract Hashing_Test is CommonTest {
);
bytes32 hash = Hashing.hashCrossDomainMessage(
_nonce,
nonce,
_sender,
_target,
_value,
......
......@@ -19,6 +19,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['ProxyAdmin', 'fresh', 'migration']
deployFn.tags = ['ProxyAdmin']
export default deployFn
......@@ -3,22 +3,22 @@ import { DeployFunction } from 'hardhat-deploy/dist/types'
import {
assertContractVariable,
deployAndVerifyAndThen,
getDeploymentAddress,
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
const proxyAdmin = await getDeploymentAddress(hre, 'ProxyAdmin')
const { deployer } = await hre.getNamedAccounts()
await deployAndVerifyAndThen({
hre,
name: 'L1StandardBridgeProxy',
contract: 'Proxy',
args: [proxyAdmin],
name: 'Lib_AddressManager',
contract: 'AddressManager',
args: [],
postDeployAction: async (contract) => {
await assertContractVariable(contract, 'admin', proxyAdmin)
// Owner is temporarily set to the deployer.
await assertContractVariable(contract, 'owner', deployer)
},
})
}
deployFn.tags = ['L1StandardBridgeProxy', 'fresh']
deployFn.tags = ['AddressManager']
export default deployFn
import { DeployFunction } from 'hardhat-deploy/dist/types'
import {
assertContractVariable,
deployAndVerifyAndThen,
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
await deployAndVerifyAndThen({
hre,
name: 'Proxy__OVM_L1StandardBridge',
contract: 'L1ChugSplashProxy',
args: [deployer],
postDeployAction: async (contract) => {
await assertContractVariable(contract, 'getOwner', deployer)
},
})
}
deployFn.tags = ['L1StandardBridgeProxy']
export default deployFn
import { DeployFunction } from 'hardhat-deploy/dist/types'
import {
assertContractVariable,
deployAndVerifyAndThen,
getDeploymentAddress,
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
const proxyAdmin = await getDeploymentAddress(hre, 'ProxyAdmin')
await deployAndVerifyAndThen({
hre,
name: 'L1CrossDomainMessengerProxy',
contract: 'Proxy',
args: [proxyAdmin],
postDeployAction: async (contract) => {
await assertContractVariable(contract, 'admin', proxyAdmin)
},
})
}
deployFn.tags = ['L1CrossDomainMessengerProxy', 'fresh']
export default deployFn
......@@ -19,6 +19,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['L2OutputOracleProxy', 'fresh', 'migration']
deployFn.tags = ['L2OutputOracleProxy']
export default deployFn
import { DeployFunction } from 'hardhat-deploy/dist/types'
import {
deployAndVerifyAndThen,
getDeploymentAddress,
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
const addressManager = await getDeploymentAddress(hre, 'Lib_AddressManager')
await deployAndVerifyAndThen({
hre,
name: 'Proxy__OVM_L1CrossDomainMessenger',
contract: 'ResolvedDelegateProxy',
args: [addressManager, 'OVM_L1CrossDomainMessenger'],
})
}
deployFn.tags = ['L1CrossDomainMessengerProxy']
export default deployFn
......@@ -19,6 +19,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['OptimismPortalProxy', 'fresh', 'migration']
deployFn.tags = ['OptimismPortalProxy']
export default deployFn
......@@ -19,6 +19,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['OptimismMintableERC20FactoryProxy', 'fresh', 'migration']
deployFn.tags = ['OptimismMintableERC20FactoryProxy']
export default deployFn
......@@ -19,6 +19,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['L1ERC721BridgeProxy', 'fresh', 'migration']
deployFn.tags = ['L1ERC721BridgeProxy']
export default deployFn
......@@ -19,6 +19,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['SystemConfigProxy', 'fresh', 'migration']
deployFn.tags = ['SystemConfigProxy']
export default deployFn
......@@ -12,6 +12,7 @@ const deployFn: DeployFunction = async (hre) => {
hre,
'OptimismPortalProxy'
)
await deployAndVerifyAndThen({
hre,
name: 'L1CrossDomainMessenger',
......@@ -31,6 +32,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['L1CrossDomainMessengerImpl', 'fresh', 'migration']
deployFn.tags = ['L1CrossDomainMessengerImpl']
export default deployFn
import { ethers } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { predeploys } from '../src'
......@@ -9,18 +8,10 @@ import {
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
let L1CrossDomainMessengerProxy: ethers.Contract
try {
L1CrossDomainMessengerProxy = await getContractFromArtifact(
hre,
'Proxy__OVM_L1CrossDomainMessenger'
)
} catch {
L1CrossDomainMessengerProxy = await getContractFromArtifact(
hre,
'L1CrossDomainMessengerProxy'
)
}
const L1CrossDomainMessengerProxy = await getContractFromArtifact(
hre,
'Proxy__OVM_L1CrossDomainMessenger'
)
await deployAndVerifyAndThen({
hre,
......@@ -41,6 +32,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['L1StandardBridgeImpl', 'fresh', 'migration']
deployFn.tags = ['L1StandardBridgeImpl']
export default deployFn
import assert from 'assert'
import { ethers } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import '@eth-optimism/hardhat-deploy-config'
......@@ -11,31 +9,15 @@ import {
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
// Use default starting time if starting time is not provided.
let deployL2StartingTimestamp =
hre.deployConfig.l2OutputOracleStartingTimestamp
if (deployL2StartingTimestamp < 0) {
const l1StartingBlock = await hre.ethers.provider.getBlock(
hre.deployConfig.l1StartingBlockTag
)
if (l1StartingBlock === null) {
throw new Error(
`Cannot fetch block tag ${hre.deployConfig.l1StartingBlockTag}`
)
}
deployL2StartingTimestamp = l1StartingBlock.timestamp
}
await deployAndVerifyAndThen({
hre,
name: 'L2OutputOracle',
args: [
hre.deployConfig.l2OutputOracleSubmissionInterval,
hre.deployConfig.l2OutputOracleGenesisL2Output,
hre.deployConfig.l2OutputOracleHistoricalTotalBlocks,
hre.deployConfig.l2OutputOracleStartingBlockNumber,
deployL2StartingTimestamp,
hre.deployConfig.l2BlockTime,
ethers.constants.HashZero,
0,
0,
hre.deployConfig.l2OutputOracleProposer,
hre.deployConfig.l2OutputOracleOwner,
],
......@@ -45,21 +27,6 @@ const deployFn: DeployFunction = async (hre) => {
'SUBMISSION_INTERVAL',
hre.deployConfig.l2OutputOracleSubmissionInterval
)
await assertContractVariable(
contract,
'STARTING_BLOCK_NUMBER',
hre.deployConfig.l2OutputOracleStartingBlockNumber
)
await assertContractVariable(
contract,
'HISTORICAL_TOTAL_BLOCKS',
hre.deployConfig.l2OutputOracleHistoricalTotalBlocks
)
await assertContractVariable(
contract,
'STARTING_TIMESTAMP',
deployL2StartingTimestamp
)
await assertContractVariable(
contract,
'L2_BLOCK_TIME',
......@@ -75,28 +42,10 @@ const deployFn: DeployFunction = async (hre) => {
'owner',
hre.deployConfig.l2OutputOracleOwner
)
// Has to be done separately since l2Output is a mapping.
if (
hre.deployConfig.l2OutputOracleGenesisL2Output ===
ethers.constants.HashZero
) {
console.log(
`[WARNING] Genesis L2 output is ZERO and should NOT BE ZERO if you are deploying to prod`
)
} else {
const output = await contract.getL2Output(
hre.deployConfig.l2OutputOracleStartingBlockNumber
)
assert(
output.outputRoot === hre.deployConfig.l2OutputOracleGenesisL2Output,
`[FATAL] L2OutputOracleImpl genesis output is ${output.outputRoot} but should be ${hre.deployConfig.l2OutputOracleGenesisL2Output}`
)
}
},
})
}
deployFn.tags = ['L2OutputOracleImpl', 'fresh', 'migration']
deployFn.tags = ['L2OutputOracleImpl']
export default deployFn
......@@ -12,6 +12,7 @@ const deployFn: DeployFunction = async (hre) => {
hre,
'L2OutputOracleProxy'
)
await deployAndVerifyAndThen({
hre,
name: 'OptimismPortal',
......@@ -34,6 +35,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['OptimismPortalImpl', 'fresh', 'migration']
deployFn.tags = ['OptimismPortalImpl']
export default deployFn
import { ethers } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import {
......@@ -8,18 +7,10 @@ import {
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
let L1StandardBridgeProxy: ethers.Contract
try {
L1StandardBridgeProxy = await getContractFromArtifact(
hre,
'Proxy__OVM_L1StandardBridge'
)
} catch (e) {
L1StandardBridgeProxy = await getContractFromArtifact(
hre,
'L1StandardBridgeProxy'
)
}
const L1StandardBridgeProxy = await getContractFromArtifact(
hre,
'Proxy__OVM_L1StandardBridge'
)
await deployAndVerifyAndThen({
hre,
......@@ -35,6 +26,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['OptimismMintableERC20FactoryImpl', 'fresh', 'migration']
deployFn.tags = ['OptimismMintableERC20FactoryImpl']
export default deployFn
import { ethers } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { predeploys } from '../src'
......@@ -9,18 +8,10 @@ import {
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
let L1CrossDomainMessengerProxy: ethers.Contract
try {
L1CrossDomainMessengerProxy = await getContractFromArtifact(
hre,
'Proxy__OVM_L1CrossDomainMessenger'
)
} catch {
L1CrossDomainMessengerProxy = await getContractFromArtifact(
hre,
'L1CrossDomainMessengerProxy'
)
}
const L1CrossDomainMessengerProxy = await getContractFromArtifact(
hre,
'Proxy__OVM_L1CrossDomainMessenger'
)
await deployAndVerifyAndThen({
hre,
......@@ -36,6 +27,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['L1ERC721BridgeImpl', 'fresh', 'migration']
deployFn.tags = ['L1ERC721BridgeImpl']
export default deployFn
import { DeployFunction } from 'hardhat-deploy/dist/types'
import '@eth-optimism/hardhat-deploy-config'
import 'hardhat-deploy'
import {
deployAndVerifyAndThen,
assertDictatorConfig,
makeDictatorConfig,
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const config = await makeDictatorConfig(
hre,
deployer,
hre.deployConfig.finalSystemOwner,
true
)
await deployAndVerifyAndThen({
hre,
name: 'FreshSystemDictator',
args: [config],
postDeployAction: async (contract) => {
await assertDictatorConfig(contract, config)
},
})
}
deployFn.tags = ['FreshSystemDictator', 'fresh']
export default deployFn
......@@ -11,6 +11,7 @@ const deployFn: DeployFunction = async (hre) => {
hre,
'OptimismPortalProxy'
)
await deployAndVerifyAndThen({
hre,
name: 'PortalSender',
......@@ -25,6 +26,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['PortalSenderImpl', 'fresh', 'migration']
deployFn.tags = ['PortalSenderImpl']
export default deployFn
import assert from 'assert'
import { ethers } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import '@eth-optimism/hardhat-deploy-config'
import 'hardhat-deploy'
import {
assertContractVariable,
getContractFromArtifact,
} from '../src/deploy-utils'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const ProxyAdmin = await getContractFromArtifact(hre, 'ProxyAdmin', {
signerOrProvider: deployer,
})
const FreshSystemDictator = await getContractFromArtifact(
hre,
'FreshSystemDictator',
{
signerOrProvider: deployer,
}
)
if ((await ProxyAdmin.owner()) !== FreshSystemDictator.address) {
console.log(`Transferring proxy admin ownership to the FreshSystemDictator`)
await ProxyAdmin.transferOwnership(FreshSystemDictator.address)
} else {
console.log(`Proxy admin already owned by the FreshSystemDictator`)
}
if ((await FreshSystemDictator.currentStep()) === 1) {
console.log(`Executing step 1`)
await FreshSystemDictator.step1()
// Check L2OutputOracle was initialized properly.
const L2OutputOracle = await getContractFromArtifact(
hre,
'L2OutputOracleProxy',
{
iface: 'L2OutputOracle',
}
)
await assertContractVariable(
L2OutputOracle,
'latestBlockNumber',
hre.deployConfig.l2OutputOracleStartingBlockNumber
)
await assertContractVariable(
L2OutputOracle,
'proposer',
hre.deployConfig.l2OutputOracleProposer
)
await assertContractVariable(
L2OutputOracle,
'owner',
hre.deployConfig.l2OutputOracleOwner
)
if (
hre.deployConfig.l2OutputOracleGenesisL2Output !==
ethers.constants.HashZero
) {
const genesisOutput = await L2OutputOracle.getL2Output(
hre.deployConfig.l2OutputOracleStartingBlockNumber
)
assert(
genesisOutput.outputRoot ===
hre.deployConfig.l2OutputOracleGenesisL2Output,
`L2OutputOracle was not initialized with the correct genesis output root`
)
}
// Check OptimismPortal was initialized properly.
const OptimismPortal = await getContractFromArtifact(
hre,
'OptimismPortalProxy',
{
iface: 'OptimismPortal',
}
)
await assertContractVariable(
OptimismPortal,
'l2Sender',
'0x000000000000000000000000000000000000dEaD'
)
const resourceParams = await OptimismPortal.params()
assert(
resourceParams.prevBaseFee.eq(await OptimismPortal.INITIAL_BASE_FEE()),
`OptimismPortal was not initialized with the correct initial base fee`
)
assert(
resourceParams.prevBoughtGas.eq(0),
`OptimismPortal was not initialized with the correct initial bought gas`
)
assert(
!resourceParams.prevBlockNum.eq(0),
`OptimismPortal was not initialized with the correct initial block number`
)
// Check L1CrossDomainMessenger was initialized properly.
const L1CrossDomainMessenger = await getContractFromArtifact(
hre,
'L1CrossDomainMessengerProxy',
{
iface: 'L1CrossDomainMessenger',
}
)
try {
await L1CrossDomainMessenger.xDomainMessageSender()
assert(false, `L1CrossDomainMessenger was not initialized properly`)
} catch (err) {
assert(
err.message.includes('xDomainMessageSender is not set'),
`L1CrossDomainMessenger was not initialized properly`
)
}
await assertContractVariable(
L1CrossDomainMessenger,
'owner',
hre.deployConfig.finalSystemOwner
)
// Check L1StandardBridge was initialized properly.
const L1StandardBridge = await getContractFromArtifact(
hre,
'L1StandardBridgeProxy',
{
iface: 'L1StandardBridge',
}
)
await assertContractVariable(
L1StandardBridge,
'messenger',
L1CrossDomainMessenger.address
)
// Check OptimismMintableERC20Factory was initialized properly.
const OptimismMintableERC20Factory = await getContractFromArtifact(
hre,
'OptimismMintableERC20FactoryProxy',
{
iface: 'OptimismMintableERC20Factory',
}
)
await assertContractVariable(
OptimismMintableERC20Factory,
'bridge',
L1StandardBridge.address
)
// Check L1ERC721Bridge was initialized properly.
const L1ERC721Bridge = await getContractFromArtifact(
hre,
'L1ERC721BridgeProxy',
{
iface: 'L1ERC721Bridge',
}
)
await assertContractVariable(
L1ERC721Bridge,
'messenger',
L1CrossDomainMessenger.address
)
} else {
console.log(`Step 1 executed`)
}
if ((await FreshSystemDictator.currentStep()) === 2) {
console.log(`Executing step 2`)
await FreshSystemDictator.step2()
// Check the ProxyAdmin owner was changed properly.
await assertContractVariable(
ProxyAdmin,
'owner',
hre.deployConfig.finalSystemOwner
)
} else {
console.log(`Step 2 executed`)
}
}
deployFn.tags = ['FreshSystemDictatorSteps', 'fresh']
export default deployFn
......@@ -11,6 +11,7 @@ const deployFn: DeployFunction = async (hre) => {
hre.deployConfig.batchSenderAddress,
32
)
await deployAndVerifyAndThen({
hre,
name: 'SystemConfig',
......@@ -46,6 +47,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['SystemConfigImpl', 'fresh', 'migration']
deployFn.tags = ['SystemConfigImpl']
export default deployFn
......@@ -46,6 +46,22 @@ const deployFn: DeployFunction = async (hre) => {
}
}
if (
hre.deployConfig.l2OutputOracleGenesisL2Output === ethers.constants.HashZero
) {
if (hre.network.config.live === false) {
console.log(`WARNING!!!`)
console.log(`WARNING!!!`)
console.log(`WARNING!!!`)
console.log(`WARNING!!! A genesis L2 output was not provided.`)
console.log(
`WARNING!!! Make sure you are ONLY doing this on a test network.`
)
} else {
throw new Error(`must specify the finalSystemOwner on live networks`)
}
}
const config = await makeDictatorConfig(hre, controller, finalOwner, false)
await deployAndVerifyAndThen({
hre,
......@@ -57,6 +73,6 @@ const deployFn: DeployFunction = async (hre) => {
})
}
deployFn.tags = ['MigrationSystemDictator', 'migration']
deployFn.tags = ['MigrationSystemDictator']
export default deployFn
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment