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

Merge branch 'develop' into aj/fpp-oracle-db

parents 421d4879 1ee68963
...@@ -620,7 +620,7 @@ jobs: ...@@ -620,7 +620,7 @@ jobs:
name: run tests name: run tests
command: | command: |
gotestsum --format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>.xml \ gotestsum --format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>.xml \
-- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out ./... -- -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out ./...
working_directory: <<parameters.module>> working_directory: <<parameters.module>>
- run: - run:
name: upload coverage name: upload coverage
...@@ -651,9 +651,10 @@ jobs: ...@@ -651,9 +651,10 @@ jobs:
command: | command: |
# Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional # Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional
# constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building. # constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building.
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=true OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \ # Note: -parallel must be set to match the number of cores in the resource class
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=false OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \
--format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \ --format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \
-- -timeout=20m ./... -- -timeout=20m -parallel=8 ./...
working_directory: <<parameters.module>> working_directory: <<parameters.module>>
- store_test_results: - store_test_results:
path: /tmp/test-results path: /tmp/test-results
......
...@@ -384,7 +384,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { ...@@ -384,7 +384,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
} }
// TestBigL2Txs tests a high-throughput case with constrained batcher: // TestBigL2Txs tests a high-throughput case with constrained batcher:
// - Fill 100 L2 blocks to near max-capacity, with txs of 120 KB each // - Fill 40 L2 blocks to near max-capacity, with txs of 120 KB each
// - Buffer the L2 blocks into channels together as much as possible, submit data-txs only when necessary // - Buffer the L2 blocks into channels together as much as possible, submit data-txs only when necessary
// (just before crossing the max RLP channel size) // (just before crossing the max RLP channel size)
// - Limit the data-tx size to 40 KB, to force data to be split across multiple datat-txs // - Limit the data-tx size to 40 KB, to force data to be split across multiple datat-txs
...@@ -428,7 +428,7 @@ func TestBigL2Txs(gt *testing.T) { ...@@ -428,7 +428,7 @@ func TestBigL2Txs(gt *testing.T) {
} }
// build many L2 blocks filled to the brim with large txs of random data // build many L2 blocks filled to the brim with large txs of random data
for i := 0; i < 100; i++ { for i := 0; i < 40; i++ {
aliceNonce, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice) aliceNonce, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice)
status := sequencer.SyncStatus() status := sequencer.SyncStatus()
// build empty L1 blocks as necessary, so the L2 sequencer can continue to include txs while not drifting too far out // build empty L1 blocks as necessary, so the L2 sequencer can continue to include txs while not drifting too far out
......
...@@ -121,6 +121,7 @@ var hardcodedSlots = []storageSlot{ ...@@ -121,6 +121,7 @@ var hardcodedSlots = []storageSlot{
} }
func TestMigration(t *testing.T) { func TestMigration(t *testing.T) {
parallel(t)
if !config.enabled { if !config.enabled {
t.Skipf("skipping migration tests") t.Skipf("skipping migration tests")
return return
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
// TestMissingGasLimit tests that op-geth cannot build a block without gas limit while optimism is active in the chain config. // TestMissingGasLimit tests that op-geth cannot build a block without gas limit while optimism is active in the chain config.
func TestMissingGasLimit(t *testing.T) { func TestMissingGasLimit(t *testing.T) {
parallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
...@@ -42,6 +43,7 @@ func TestMissingGasLimit(t *testing.T) { ...@@ -42,6 +43,7 @@ func TestMissingGasLimit(t *testing.T) {
// TestInvalidDepositInFCU runs an invalid deposit through a FCU/GetPayload/NewPayload/FCU set of calls. // TestInvalidDepositInFCU runs an invalid deposit through a FCU/GetPayload/NewPayload/FCU set of calls.
// This tests that deposits must always allow the block to be built even if they are invalid. // This tests that deposits must always allow the block to be built even if they are invalid.
func TestInvalidDepositInFCU(t *testing.T) { func TestInvalidDepositInFCU(t *testing.T) {
parallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
...@@ -76,6 +78,7 @@ func TestInvalidDepositInFCU(t *testing.T) { ...@@ -76,6 +78,7 @@ func TestInvalidDepositInFCU(t *testing.T) {
} }
func TestPreregolith(t *testing.T) { func TestPreregolith(t *testing.T) {
parallel(t)
futureTimestamp := hexutil.Uint64(4) futureTimestamp := hexutil.Uint64(4)
tests := []struct { tests := []struct {
name string name string
...@@ -255,6 +258,7 @@ func TestPreregolith(t *testing.T) { ...@@ -255,6 +258,7 @@ func TestPreregolith(t *testing.T) {
} }
func TestRegolith(t *testing.T) { func TestRegolith(t *testing.T) {
parallel(t)
tests := []struct { tests := []struct {
name string name string
regolithTime hexutil.Uint64 regolithTime hexutil.Uint64
......
...@@ -425,6 +425,8 @@ func TestMixedWithdrawalValidity(t *testing.T) { ...@@ -425,6 +425,8 @@ func TestMixedWithdrawalValidity(t *testing.T) {
for i := 0; i <= 8; i++ { for i := 0; i <= 8; i++ {
i := i // avoid loop var capture i := i // avoid loop var capture
t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) { t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) {
parallel(t)
// Create our system configuration, funding all accounts we created for L1/L2, and start it // Create our system configuration, funding all accounts we created for L1/L2, and start it
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 6 cfg.DeployConfig.FinalizationPeriodSeconds = 6
......
package engineapi
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)
var (
ErrExceedsGasLimit = errors.New("tx gas exceeds block gas limit")
ErrUsesTooMuchGas = errors.New("action takes too much gas")
)
type BlockDataProvider interface {
StateAt(root common.Hash) (*state.StateDB, error)
GetHeader(common.Hash, uint64) *types.Header
Engine() consensus.Engine
GetVMConfig() *vm.Config
Config() *params.ChainConfig
consensus.ChainHeaderReader
}
type BlockProcessor struct {
header *types.Header
state *state.StateDB
receipts types.Receipts
transactions types.Transactions
gasPool *core.GasPool
dataProvider BlockDataProvider
}
func NewBlockProcessorFromPayloadAttributes(provider BlockDataProvider, parent common.Hash, params *eth.PayloadAttributes) (*BlockProcessor, error) {
header := &types.Header{
ParentHash: parent,
Coinbase: params.SuggestedFeeRecipient,
Difficulty: common.Big0,
GasLimit: uint64(*params.GasLimit),
Time: uint64(params.Timestamp),
Extra: nil,
MixDigest: common.Hash(params.PrevRandao),
Nonce: types.EncodeNonce(0),
}
return NewBlockProcessorFromHeader(provider, header)
}
func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*BlockProcessor, error) {
header := *h // Copy to avoid mutating the original header
if header.GasLimit > params.MaxGasLimit {
return nil, fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
}
parentHeader := provider.GetHeaderByHash(header.ParentHash)
if header.Time <= parentHeader.Time {
return nil, errors.New("invalid timestamp")
}
statedb, err := provider.StateAt(parentHeader.Root)
if err != nil {
return nil, fmt.Errorf("get parent state: %w", err)
}
header.Number = new(big.Int).Add(parentHeader.Number, common.Big1)
header.BaseFee = misc.CalcBaseFee(provider.Config(), parentHeader)
header.GasUsed = 0
gasPool := new(core.GasPool).AddGas(header.GasLimit)
return &BlockProcessor{
header: &header,
state: statedb,
gasPool: gasPool,
dataProvider: provider,
}, nil
}
func (b *BlockProcessor) CheckTxWithinGasLimit(tx *types.Transaction) error {
if tx.Gas() > b.header.GasLimit {
return fmt.Errorf("%w tx gas: %d, block gas limit: %d", ErrExceedsGasLimit, tx.Gas(), b.header.GasLimit)
}
if tx.Gas() > b.gasPool.Gas() {
return fmt.Errorf("%w: %d, only have %d", ErrUsesTooMuchGas, tx.Gas(), b.gasPool.Gas())
}
return nil
}
func (b *BlockProcessor) AddTx(tx *types.Transaction) error {
txIndex := len(b.transactions)
b.state.SetTxContext(tx.Hash(), txIndex)
receipt, err := core.ApplyTransaction(b.dataProvider.Config(), b.dataProvider, &b.header.Coinbase,
b.gasPool, b.state, b.header, tx, &b.header.GasUsed, *b.dataProvider.GetVMConfig())
if err != nil {
return fmt.Errorf("failed to apply deposit transaction to L2 block (tx %d): %w", txIndex, err)
}
b.receipts = append(b.receipts, receipt)
b.transactions = append(b.transactions, tx)
return nil
}
func (b *BlockProcessor) Assemble() (*types.Block, error) {
return b.dataProvider.Engine().FinalizeAndAssemble(b.dataProvider, b.header, b.state, b.transactions, nil, b.receipts, nil)
}
func (b *BlockProcessor) Commit() error {
root, err := b.state.Commit(b.dataProvider.Config().IsEIP158(b.header.Number))
if err != nil {
return fmt.Errorf("state write error: %w", err)
}
if err := b.state.Database().TrieDB().Commit(root, false); err != nil {
return fmt.Errorf("trie write error: %w", err)
}
return nil
}
...@@ -6,33 +6,25 @@ import ( ...@@ -6,33 +6,25 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum-optimism/optimism/op-node/eth"
) )
type EngineBackend interface { type EngineBackend interface {
CurrentBlock() *types.Header CurrentBlock() *types.Header
CurrentSafeBlock() *types.Header CurrentSafeBlock() *types.Header
CurrentFinalBlock() *types.Header CurrentFinalBlock() *types.Header
GetHeaderByHash(hash common.Hash) *types.Header
GetBlockByHash(hash common.Hash) *types.Block GetBlockByHash(hash common.Hash) *types.Block
GetBlock(hash common.Hash, number uint64) *types.Block GetBlock(hash common.Hash, number uint64) *types.Block
// GetHeader returns the header corresponding to the hash/number argument pair.
GetHeader(common.Hash, uint64) *types.Header
HasBlockAndState(hash common.Hash, number uint64) bool HasBlockAndState(hash common.Hash, number uint64) bool
GetCanonicalHash(n uint64) common.Hash GetCanonicalHash(n uint64) common.Hash
...@@ -47,6 +39,8 @@ type EngineBackend interface { ...@@ -47,6 +39,8 @@ type EngineBackend interface {
SetCanonical(head *types.Block) (common.Hash, error) SetCanonical(head *types.Block) (common.Hash, error)
SetFinalized(header *types.Header) SetFinalized(header *types.Header)
SetSafe(header *types.Header) SetSafe(header *types.Header)
consensus.ChainHeaderReader
} }
// L2EngineAPI wraps an engine actor, and implements the RPC backend required to serve the engine API. // L2EngineAPI wraps an engine actor, and implements the RPC backend required to serve the engine API.
...@@ -57,14 +51,10 @@ type L2EngineAPI struct { ...@@ -57,14 +51,10 @@ type L2EngineAPI struct {
backend EngineBackend backend EngineBackend
// L2 block building data // L2 block building data
l2BuildingHeader *types.Header // block header that we add txs to for block building blockProcessor *BlockProcessor
l2BuildingState *state.StateDB // state used for block building pendingIndices map[common.Address]uint64 // per account, how many txs from the pool were already included in the block, since the pool is lagging behind block mining.
l2GasPool *core.GasPool // track gas used of ongoing building l2ForceEmpty bool // when no additional txs may be processed (i.e. when sequencer drift runs out)
pendingIndices map[common.Address]uint64 // per account, how many txs from the pool were already included in the block, since the pool is lagging behind block mining. l2TxFailed []*types.Transaction // log of failed transactions which could not be included
l2Transactions []*types.Transaction // collects txs that were successfully included into current block build
l2Receipts []*types.Receipt // collect receipts of ongoing building
l2ForceEmpty bool // when no additional txs may be processed (i.e. when sequencer drift runs out)
l2TxFailed []*types.Transaction // log of failed transactions which could not be included
payloadID engine.PayloadID // ID of payload that is currently being built payloadID engine.PayloadID // ID of payload that is currently being built
} }
...@@ -102,7 +92,10 @@ func computePayloadId(headBlockHash common.Hash, params *eth.PayloadAttributes) ...@@ -102,7 +92,10 @@ func computePayloadId(headBlockHash common.Hash, params *eth.PayloadAttributes)
} }
func (ea *L2EngineAPI) RemainingBlockGas() uint64 { func (ea *L2EngineAPI) RemainingBlockGas() uint64 {
return ea.l2GasPool.Gas() if ea.blockProcessor == nil {
return 0
}
return ea.blockProcessor.gasPool.Gas()
} }
func (ea *L2EngineAPI) ForcedEmpty() bool { func (ea *L2EngineAPI) ForcedEmpty() bool {
...@@ -115,12 +108,10 @@ func (ea *L2EngineAPI) PendingIndices(from common.Address) uint64 { ...@@ -115,12 +108,10 @@ func (ea *L2EngineAPI) PendingIndices(from common.Address) uint64 {
var ( var (
ErrNotBuildingBlock = errors.New("not currently building a block, cannot include tx from queue") ErrNotBuildingBlock = errors.New("not currently building a block, cannot include tx from queue")
ErrExceedsGasLimit = errors.New("tx gas exceeds block gas limit")
ErrUsesTooMuchGas = errors.New("action takes too much gas")
) )
func (ea *L2EngineAPI) IncludeTx(tx *types.Transaction, from common.Address) error { func (ea *L2EngineAPI) IncludeTx(tx *types.Transaction, from common.Address) error {
if ea.l2BuildingHeader == nil { if ea.blockProcessor == nil {
return ErrNotBuildingBlock return ErrNotBuildingBlock
} }
if ea.l2ForceEmpty { if ea.l2ForceEmpty {
...@@ -129,60 +120,32 @@ func (ea *L2EngineAPI) IncludeTx(tx *types.Transaction, from common.Address) err ...@@ -129,60 +120,32 @@ func (ea *L2EngineAPI) IncludeTx(tx *types.Transaction, from common.Address) err
return nil return nil
} }
if tx.Gas() > ea.l2BuildingHeader.GasLimit { err := ea.blockProcessor.CheckTxWithinGasLimit(tx)
return fmt.Errorf("%w tx gas: %d, block gas limit: %d", ErrExceedsGasLimit, tx.Gas(), ea.l2BuildingHeader.GasLimit) if err != nil {
} return err
if tx.Gas() > uint64(*ea.l2GasPool) {
return fmt.Errorf("%w: %d, only have %d", ErrUsesTooMuchGas, tx.Gas(), uint64(*ea.l2GasPool))
} }
ea.pendingIndices[from] = ea.pendingIndices[from] + 1 // won't retry the tx ea.pendingIndices[from] = ea.pendingIndices[from] + 1 // won't retry the tx
ea.l2BuildingState.SetTxContext(tx.Hash(), len(ea.l2Transactions)) err = ea.blockProcessor.AddTx(tx)
receipt, err := core.ApplyTransaction(ea.backend.Config(), ea.backend, &ea.l2BuildingHeader.Coinbase,
ea.l2GasPool, ea.l2BuildingState, ea.l2BuildingHeader, tx, &ea.l2BuildingHeader.GasUsed, *ea.backend.GetVMConfig())
if err != nil { if err != nil {
ea.l2TxFailed = append(ea.l2TxFailed, tx) ea.l2TxFailed = append(ea.l2TxFailed, tx)
return fmt.Errorf("invalid L2 block (tx %d): %w", len(ea.l2Transactions), err) return fmt.Errorf("invalid L2 block (tx %d): %w", len(ea.blockProcessor.transactions), err)
} }
ea.l2Receipts = append(ea.l2Receipts, receipt)
ea.l2Transactions = append(ea.l2Transactions, tx)
return nil return nil
} }
func (ea *L2EngineAPI) startBlock(parent common.Hash, params *eth.PayloadAttributes) error { func (ea *L2EngineAPI) startBlock(parent common.Hash, params *eth.PayloadAttributes) error {
if ea.l2BuildingHeader != nil { if ea.blockProcessor != nil {
ea.log.Warn("started building new block without ending previous block", "previous", ea.l2BuildingHeader, "prev_payload_id", ea.payloadID) ea.log.Warn("started building new block without ending previous block", "previous", ea.blockProcessor.header, "prev_payload_id", ea.payloadID)
} }
parentHeader := ea.backend.GetHeaderByHash(parent) processor, err := NewBlockProcessorFromPayloadAttributes(ea.backend, parent, params)
if parentHeader == nil {
return fmt.Errorf("uknown parent block: %s", parent)
}
statedb, err := ea.backend.StateAt(parentHeader.Root)
if err != nil { if err != nil {
return fmt.Errorf("failed to init state db around block %s (state %s): %w", parent, parentHeader.Root, err) return err
}
header := &types.Header{
ParentHash: parent,
Coinbase: params.SuggestedFeeRecipient,
Difficulty: common.Big0,
Number: new(big.Int).Add(parentHeader.Number, common.Big1),
GasLimit: uint64(*params.GasLimit),
Time: uint64(params.Timestamp),
Extra: nil,
MixDigest: common.Hash(params.PrevRandao),
} }
ea.blockProcessor = processor
header.BaseFee = misc.CalcBaseFee(ea.backend.Config(), parentHeader)
ea.l2BuildingHeader = header
ea.l2BuildingState = statedb
ea.l2Receipts = make([]*types.Receipt, 0)
ea.l2Transactions = make([]*types.Transaction, 0)
ea.pendingIndices = make(map[common.Address]uint64) ea.pendingIndices = make(map[common.Address]uint64)
ea.l2ForceEmpty = params.NoTxPool ea.l2ForceEmpty = params.NoTxPool
ea.l2GasPool = new(core.GasPool).AddGas(header.GasLimit)
ea.payloadID = computePayloadId(parent, params) ea.payloadID = computePayloadId(parent, params)
// pre-process the deposits // pre-process the deposits
...@@ -191,29 +154,26 @@ func (ea *L2EngineAPI) startBlock(parent common.Hash, params *eth.PayloadAttribu ...@@ -191,29 +154,26 @@ func (ea *L2EngineAPI) startBlock(parent common.Hash, params *eth.PayloadAttribu
if err := tx.UnmarshalBinary(otx); err != nil { if err := tx.UnmarshalBinary(otx); err != nil {
return fmt.Errorf("transaction %d is not valid: %w", i, err) return fmt.Errorf("transaction %d is not valid: %w", i, err)
} }
ea.l2BuildingState.SetTxContext(tx.Hash(), i) err := ea.blockProcessor.AddTx(&tx)
receipt, err := core.ApplyTransaction(ea.backend.Config(), ea.backend, &ea.l2BuildingHeader.Coinbase,
ea.l2GasPool, ea.l2BuildingState, ea.l2BuildingHeader, &tx, &ea.l2BuildingHeader.GasUsed, *ea.backend.GetVMConfig())
if err != nil { if err != nil {
ea.l2TxFailed = append(ea.l2TxFailed, &tx) ea.l2TxFailed = append(ea.l2TxFailed, &tx)
return fmt.Errorf("failed to apply deposit transaction to L2 block (tx %d): %w", i, err) return fmt.Errorf("failed to apply deposit transaction to L2 block (tx %d): %w", i, err)
} }
ea.l2Receipts = append(ea.l2Receipts, receipt)
ea.l2Transactions = append(ea.l2Transactions, &tx)
} }
return nil return nil
} }
func (ea *L2EngineAPI) endBlock() (*types.Block, error) { func (ea *L2EngineAPI) endBlock() (*types.Block, error) {
if ea.l2BuildingHeader == nil { if ea.blockProcessor == nil {
return nil, fmt.Errorf("no block is being built currently (id %s)", ea.payloadID) return nil, fmt.Errorf("no block is being built currently (id %s)", ea.payloadID)
} }
header := ea.l2BuildingHeader processor := ea.blockProcessor
ea.l2BuildingHeader = nil ea.blockProcessor = nil
header.GasUsed = header.GasLimit - uint64(*ea.l2GasPool) block, err := processor.Assemble()
header.Root = ea.l2BuildingState.IntermediateRoot(ea.backend.Config().IsEIP158(header.Number)) if err != nil {
block := types.NewBlock(header, ea.l2Transactions, nil, ea.l2Receipts, trie.NewStackTrie(nil)) return nil, fmt.Errorf("assemble block: %w", err)
}
return block, nil return block, nil
} }
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -36,6 +37,38 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken ...@@ -36,6 +37,38 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
api.assert.Equal(block.BlockHash, api.headHash(), "should create and import new block") api.assert.Equal(block.BlockHash, api.headHash(), "should create and import new block")
imported := api.backend.GetBlockByHash(block.BlockHash) imported := api.backend.GetBlockByHash(block.BlockHash)
api.assert.Len(imported.Transactions(), 1, "should include transaction") api.assert.Len(imported.Transactions(), 1, "should include transaction")
api.assert.NotEqual(genesis.Root, block.StateRoot)
newState, err := api.backend.StateAt(common.Hash(block.StateRoot))
require.NoError(t, err, "imported block state should be available")
require.NotNil(t, newState)
})
t.Run("RejectCreatingBlockWithInvalidRequiredTransaction", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
txData, err := derive.L1InfoDeposit(1, eth.HeaderBlockInfo(genesis), eth.SystemConfig{}, true)
api.assert.NoError(err)
txData.Gas = uint64(gasLimit + 1)
tx := types.NewTx(txData)
txRlp, err := tx.MarshalBinary()
api.assert.NoError(err)
result, err := api.engine.ForkchoiceUpdatedV1(api.ctx, &eth.ForkchoiceState{
HeadBlockHash: genesis.Hash(),
SafeBlockHash: genesis.Hash(),
FinalizedBlockHash: genesis.Hash(),
}, &eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(genesis.Time + 1),
PrevRandao: eth.Bytes32(genesis.MixDigest),
SuggestedFeeRecipient: feeRecipient,
Transactions: []eth.Data{txRlp},
NoTxPool: true,
GasLimit: &gasLimit,
})
api.assert.Error(err)
api.assert.Equal(eth.ExecutionInvalid, result.PayloadStatus.Status)
}) })
t.Run("IgnoreUpdateHeadToOlderBlock", func(t *testing.T) { t.Run("IgnoreUpdateHeadToOlderBlock", func(t *testing.T) {
...@@ -98,9 +131,14 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken ...@@ -98,9 +131,14 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
api := newTestHelper(t, createBackend) api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock() genesis := api.backend.CurrentBlock()
payloadID := api.startBlockBuilding(genesis, eth.Uint64Quantity(genesis.Time)) // Start with a valid time
payloadID := api.startBlockBuilding(genesis, eth.Uint64Quantity(genesis.Time+1))
newBlock := api.getPayload(payloadID) newBlock := api.getPayload(payloadID)
// Then make it invalid to check NewPayload rejects it
newBlock.Timestamp = eth.Uint64Quantity(genesis.Time)
updateBlockHash(newBlock)
r, err := api.engine.NewPayloadV1(api.ctx, newBlock) r, err := api.engine.NewPayloadV1(api.ctx, newBlock)
api.assert.NoError(err) api.assert.NoError(err)
api.assert.Equal(eth.ExecutionInvalid, r.Status) api.assert.Equal(eth.ExecutionInvalid, r.Status)
...@@ -110,14 +148,81 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken ...@@ -110,14 +148,81 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
api := newTestHelper(t, createBackend) api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock() genesis := api.backend.CurrentBlock()
payloadID := api.startBlockBuilding(genesis, eth.Uint64Quantity(genesis.Time-1)) // Start with a valid time
payloadID := api.startBlockBuilding(genesis, eth.Uint64Quantity(genesis.Time+1))
newBlock := api.getPayload(payloadID) newBlock := api.getPayload(payloadID)
// Then make it invalid to check NewPayload rejects it
newBlock.Timestamp = eth.Uint64Quantity(genesis.Time - 1)
updateBlockHash(newBlock)
r, err := api.engine.NewPayloadV1(api.ctx, newBlock) r, err := api.engine.NewPayloadV1(api.ctx, newBlock)
api.assert.NoError(err) api.assert.NoError(err)
api.assert.Equal(eth.ExecutionInvalid, r.Status) api.assert.Equal(eth.ExecutionInvalid, r.Status)
}) })
t.Run("RejectCreateBlockWithSameTimeAsParent", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
result, err := api.engine.ForkchoiceUpdatedV1(api.ctx, &eth.ForkchoiceState{
HeadBlockHash: genesis.Hash(),
SafeBlockHash: genesis.Hash(),
FinalizedBlockHash: genesis.Hash(),
}, &eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(genesis.Time),
PrevRandao: eth.Bytes32(genesis.MixDigest),
SuggestedFeeRecipient: feeRecipient,
Transactions: nil,
NoTxPool: true,
GasLimit: &gasLimit,
})
api.assert.Error(err)
api.assert.Equal(eth.ExecutionInvalid, result.PayloadStatus.Status)
})
t.Run("RejectCreateBlockWithTimeBeforeParent", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
result, err := api.engine.ForkchoiceUpdatedV1(api.ctx, &eth.ForkchoiceState{
HeadBlockHash: genesis.Hash(),
SafeBlockHash: genesis.Hash(),
FinalizedBlockHash: genesis.Hash(),
}, &eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(genesis.Time - 1),
PrevRandao: eth.Bytes32(genesis.MixDigest),
SuggestedFeeRecipient: feeRecipient,
Transactions: nil,
NoTxPool: true,
GasLimit: &gasLimit,
})
api.assert.Error(err)
api.assert.Equal(eth.ExecutionInvalid, result.PayloadStatus.Status)
})
t.Run("RejectCreateBlockWithGasLimitAboveMax", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
gasLimit := eth.Uint64Quantity(params.MaxGasLimit + 1)
result, err := api.engine.ForkchoiceUpdatedV1(api.ctx, &eth.ForkchoiceState{
HeadBlockHash: genesis.Hash(),
SafeBlockHash: genesis.Hash(),
FinalizedBlockHash: genesis.Hash(),
}, &eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(genesis.Time + 1),
PrevRandao: eth.Bytes32(genesis.MixDigest),
SuggestedFeeRecipient: feeRecipient,
Transactions: nil,
NoTxPool: true,
GasLimit: &gasLimit,
})
api.assert.Error(err)
api.assert.Equal(eth.ExecutionInvalid, result.PayloadStatus.Status)
})
t.Run("UpdateSafeAndFinalizedHead", func(t *testing.T) { t.Run("UpdateSafeAndFinalizedHead", func(t *testing.T) {
api := newTestHelper(t, createBackend) api := newTestHelper(t, createBackend)
......
...@@ -5,9 +5,11 @@ const config: DeployConfig = { ...@@ -5,9 +5,11 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '', l2ProxyOwnerAddress: '',
optimistName: '', optimistName: '',
optimistSymbol: '', optimistSymbol: '',
attestorAddress: '', optimistBaseUriAttestorAddress: '',
optimistInviterName: '',
optimistInviterInviteGranter: '', optimistInviterInviteGranter: '',
optimistInviterName: '',
optimistAllowlistAllowlistAttestor: '',
optimistAllowlistCoinbaseQuestAttestor: '',
} }
export default config export default config
...@@ -5,9 +5,13 @@ const config: DeployConfig = { ...@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', optimistBaseUriAttestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter', optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistAllowlistCoinbaseQuestAttestor:
'0x661B7Acca8ebd93AFd349a088e9a9A00053DB1BF',
} }
export default config export default config
...@@ -5,9 +5,13 @@ const config: DeployConfig = { ...@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', l2ProxyOwnerAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistName: 'OP Citizenship', optimistName: 'OP Citizenship',
optimistSymbol: 'OPNFT', optimistSymbol: 'OPNFT',
attestorAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', optimistBaseUriAttestorAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistInviterInviteGranter: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', optimistInviterInviteGranter: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistInviterName: 'OptimistInviter', optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistAllowlistCoinbaseQuestAttestor:
'0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
} }
export default config export default config
import config from './hardhat'
// uses the same config as hardhat.ts
export default config
...@@ -5,9 +5,13 @@ const config: DeployConfig = { ...@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', optimistBaseUriAttestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter', optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistAllowlistCoinbaseQuestAttestor:
'0x661B7Acca8ebd93AFd349a088e9a9A00053DB1BF',
} }
export default config export default config
...@@ -2,12 +2,17 @@ import { DeployConfig } from '../../src' ...@@ -2,12 +2,17 @@ import { DeployConfig } from '../../src'
const config: DeployConfig = { const config: DeployConfig = {
ddd: '0x9C6373dE60c2D3297b18A8f964618ac46E011B58', ddd: '0x9C6373dE60c2D3297b18A8f964618ac46E011B58',
// EcoPod test account
l2ProxyOwnerAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819', l2ProxyOwnerAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819', optimistBaseUriAttestorAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistInviterInviteGranter: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819', optimistInviterInviteGranter: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistInviterName: 'OptimistInviter', optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistAllowlistCoinbaseQuestAttestor:
'0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
} }
export default config export default config
...@@ -5,9 +5,13 @@ const config: DeployConfig = { ...@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', optimistBaseUriAttestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter', optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistAllowlistCoinbaseQuestAttestor:
'0x661B7Acca8ebd93AFd349a088e9a9A00053DB1BF',
} }
export default config export default config
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers'
import '@eth-optimism/hardhat-deploy-config'
import 'hardhat-deploy'
import type { DeployConfig } from '../../src'
const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const deployConfig = hre.deployConfig as DeployConfig
const { deployer } = await hre.getNamedAccounts()
console.log(`Deploying OptimistAllowlist implementation with ${deployer}`)
const Deployment__AttestationStation = await hre.deployments.get(
'AttestationStationProxy'
)
const Deployment__OptimistInviter = await hre.deployments.get(
'OptimistInviterProxy'
)
const attestationStationAddress = Deployment__AttestationStation.address
const optimistInviterAddress = Deployment__OptimistInviter.address
console.log(`Using ${attestationStationAddress} as the ATTESTATION_STATION`)
console.log(
`Using ${deployConfig.optimistAllowlistAllowlistAttestor} as ALLOWLIST_ATTESTOR`
)
console.log(
`Using ${deployConfig.optimistAllowlistCoinbaseQuestAttestor} as COINBASE_QUEST_ATTESTOR`
)
console.log(`Using ${optimistInviterAddress} as OPTIMIST_INVITER`)
const { deploy } = await hre.deployments.deterministic('OptimistAllowlist', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['OptimistAllowlist']),
from: deployer,
args: [
attestationStationAddress,
deployConfig.optimistAllowlistAllowlistAttestor,
deployConfig.optimistAllowlistCoinbaseQuestAttestor,
optimistInviterAddress,
],
log: true,
})
await deploy()
}
deployFn.tags = ['OptimistAllowlistImpl', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistInviterProxy']
export default deployFn
/* Imports: External */
import assert from 'assert'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import '@eth-optimism/hardhat-deploy-config'
import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy'
import { assertContractVariable } from '@eth-optimism/contracts-bedrock/src/deploy-utils'
import { ethers, utils } from 'ethers'
import type { DeployConfig } from '../../src'
import { setupProxyContract } from '../../src/helpers/setupProxyContract'
const { getAddress } = utils
// Required conditions before deploying - Specified in `deployFn.dependencies`
// - AttestationStationProxy is deployed and points to the correct implementation
// - OptimistInviterProxy is deployed and points to the correct implementation
// - OptimistAllowlistImpl is deployed
//
// Steps
// 1. Deploy OptimistAllowlistProxy
// 2. Point the newly deployed proxy to the implementation, if it hasn't been done already
// 3. Update the admin of the proxy to the l2ProxyOwnerAddress, if it hasn't been done already
// 4. Basic sanity checks for contract variables
const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const deployConfig = hre.deployConfig as DeployConfig
// Deployer should be set in hardhat.config.ts
const { deployer } = await hre.getNamedAccounts()
// We want the ability to deploy to a deterministic address, so we need the init bytecode to be
// consistent across deployments. The ddd will quickly transfer the ownership of the Proxy to a
// multisig after deployment.
//
// We need a consistent ddd, since the Proxy takes a `_admin` constructor argument, which
// affects the init bytecode and hence deployed address.
const ddd = deployConfig.ddd
if (getAddress(deployer) !== getAddress(ddd)) {
// Not a hard requirement. We can deploy with any account and just set the `_admin` to the
// ddd, but requiring that the deployer is the same as the ddd minimizes number of hot wallets
// we need to keep track of during deployment.
throw new Error('Must deploy with the ddd')
}
// Get the up to date deployment of the OptimistAllowlist contract
const Deployment__OptimistAllowlistImpl = await hre.deployments.get(
'OptimistAllowlist'
)
console.log(`Deploying OptimistAllowlistProxy with ${deployer}`)
// Deploys the Proxy.sol contract with the `_admin` constructor param set to the ddd (=== deployer).
const { deploy } = await hre.deployments.deterministic(
'OptimistAllowlistProxy',
{
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['OptimistAllowlistProxy']
),
contract: 'Proxy',
from: deployer,
args: [deployer],
log: true,
}
)
// Deploy the Proxy contract
await deploy()
const Deployment__OptimistAllowlistProxy = await hre.deployments.get(
'OptimistAllowlistProxy'
)
console.log(
`OptimistAllowlistProxy deployed to ${Deployment__OptimistAllowlistProxy.address}`
)
// Deployed Proxy.sol contract
const Proxy = await hre.ethers.getContractAt(
'Proxy',
Deployment__OptimistAllowlistProxy.address
)
// Deployed Proxy.sol contract with the OptimistAllowlist interface
const OptimistAllowlist = await hre.ethers.getContractAt(
'OptimistAllowlist',
Deployment__OptimistAllowlistProxy.address
)
// ethers.Signer for the ddd. Should be the current owner of the Proxy.
const dddSigner = await hre.ethers.provider.getSigner(deployer)
// intended admin of the Proxy
const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress
// setup the Proxy contract with correct implementation and admin
await setupProxyContract(Proxy, dddSigner, {
targetImplAddress: Deployment__OptimistAllowlistImpl.address,
targetProxyOwnerAddress: l2ProxyOwnerAddress,
})
const Deployment__AttestationStationProxy = await hre.deployments.get(
'AttestationStationProxy'
)
const Deployment__OptimistInviter = await hre.deployments.get(
'OptimistInviterProxy'
)
await assert(
getAddress(
await Proxy.connect(ethers.constants.AddressZero).callStatic.admin()
) === getAddress(l2ProxyOwnerAddress)
)
await assertContractVariable(OptimistAllowlist, 'version', '1.0.0')
await assertContractVariable(
OptimistAllowlist,
'ATTESTATION_STATION',
Deployment__AttestationStationProxy.address
)
await assertContractVariable(
OptimistAllowlist,
'ALLOWLIST_ATTESTOR',
deployConfig.optimistAllowlistAllowlistAttestor
)
await assertContractVariable(
OptimistAllowlist,
'COINBASE_QUEST_ATTESTOR',
deployConfig.optimistAllowlistCoinbaseQuestAttestor
)
await assertContractVariable(
OptimistAllowlist,
'OPTIMIST_INVITER',
Deployment__OptimistInviter.address
)
}
deployFn.tags = ['OptimistAllowlistProxy', 'OptimistEnvironment']
deployFn.dependencies = [
'AttestationStationProxy',
'OptimistInviterProxy',
'OptimistAllowlistImpl',
]
export default deployFn
...@@ -14,13 +14,19 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -14,13 +14,19 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
console.log(`Deploying Optimist implementation with ${deployer}`) console.log(`Deploying Optimist implementation with ${deployer}`)
const Deployment__AttestationStation = await hre.deployments.get( const Deployment__AttestationStationProxy = await hre.deployments.get(
'AttestationStationProxy' 'AttestationStationProxy'
) )
const attestationStationAddress = Deployment__AttestationStation.address const attestationStationAddress = Deployment__AttestationStationProxy.address
console.log(`Using ${attestationStationAddress} as the ATTESTATION_STATION`)
console.log(
`Using ${deployConfig.optimistBaseUriAttestorAddress} as BASE_URI_ATTESTOR`
)
console.log(`Using ${attestationStationAddress} as the AttestationStation`) const Deployment__OptimistAllowlistProxy = await hre.deployments.get(
console.log(`Using ${deployConfig.attestorAddress} as ATTESTOR`) 'OptimistAllowlistProxy'
)
const optimistAllowlistAddress = Deployment__OptimistAllowlistProxy.address
const { deploy } = await hre.deployments.deterministic('Optimist', { const { deploy } = await hre.deployments.deterministic('Optimist', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['Optimist']), salt: hre.ethers.utils.solidityKeccak256(['string'], ['Optimist']),
...@@ -28,8 +34,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -28,8 +34,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
args: [ args: [
deployConfig.optimistName, deployConfig.optimistName,
deployConfig.optimistSymbol, deployConfig.optimistSymbol,
deployConfig.attestorAddress, deployConfig.optimistBaseUriAttestorAddress,
attestationStationAddress, attestationStationAddress,
optimistAllowlistAddress,
], ],
log: true, log: true,
}) })
...@@ -37,6 +44,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -37,6 +44,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
await deploy() await deploy()
} }
deployFn.tags = ['Optimist', 'OptimistEnvironment'] deployFn.tags = ['OptimistImpl', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistAllowlistProxy']
export default deployFn export default deployFn
...@@ -37,7 +37,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -37,7 +37,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
await deploy() await deploy()
} }
deployFn.tags = ['OptimistInviter', 'OptimistEnvironment'] deployFn.tags = ['OptimistInviterImpl', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy'] deployFn.dependencies = ['AttestationStationProxy']
export default deployFn export default deployFn
...@@ -10,6 +10,7 @@ import { assertContractVariable } from '@eth-optimism/contracts-bedrock/src/depl ...@@ -10,6 +10,7 @@ import { assertContractVariable } from '@eth-optimism/contracts-bedrock/src/depl
import { ethers, utils } from 'ethers' import { ethers, utils } from 'ethers'
import type { DeployConfig } from '../../src' import type { DeployConfig } from '../../src'
import { setupProxyContract } from '../../src/helpers/setupProxyContract'
const { getAddress } = utils const { getAddress } = utils
...@@ -73,7 +74,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -73,7 +74,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
'OptimistInviterProxy' 'OptimistInviterProxy'
) )
console.log( console.log(
`OptimistProxy deployed to ${Deployment__OptimistInviterProxy.address}` `OptimistInviterProxy deployed to ${Deployment__OptimistInviterProxy.address}`
) )
// Deployed Proxy.sol contract // Deployed Proxy.sol contract
...@@ -88,79 +89,25 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -88,79 +89,25 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
Deployment__OptimistInviterProxy.address Deployment__OptimistInviterProxy.address
) )
// Gets the current implementation address the proxy is pointing to. const name = deployConfig.optimistInviterName
// callStatic is used since the `Proxy.implementation()` is not a view function and ethers will // Create the calldata for the call to `initialize()`
// try to make a transaction if we don't use callStatic. Using the zero address as `from` lets us const initializeCalldata = OptimistInviter.interface.encodeFunctionData(
// call functions on the proxy and not trigger the delegatecall. See Proxy.sol proxyCallIfNotAdmin 'initialize',
// modifier for more details. [name]
const implementation = await Proxy.connect( )
ethers.constants.AddressZero
).callStatic.implementation()
console.log(`implementation set to ${implementation}`)
if (
getAddress(implementation) !==
getAddress(Deployment__OptimistInviterImpl.address)
) {
// If the proxy isn't pointing to the correct implementation, we need to set it to the correct
// one, then call initialize() in the proxy's context.
console.log(
'implementation not set to OptimistInviter implementation contract'
)
console.log(
`Setting implementation to ${Deployment__OptimistInviterImpl.address}`
)
const name = deployConfig.optimistInviterName
// Create the calldata for the call to `initialize()`
const calldata = OptimistInviter.interface.encodeFunctionData(
'initialize',
[name]
)
// ethers.Signer for the ddd
const dddSigner = await hre.ethers.provider.getSigner(deployer)
// Point the proxy to the deployed OptimistInviter implementation contract,
// and call `initialize()` in the proxy's context
const tx = await Proxy.connect(dddSigner).upgradeToAndCall(
Deployment__OptimistInviterImpl.address,
calldata
)
const receipt = await tx.wait()
console.log(`implementation set in ${receipt.transactionHash}`)
} else {
console.log(
'implementation already set to OptimistInviter implementation contract'
)
}
const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress
// Get the current proxy admin address
const admin = await Proxy.connect(
ethers.constants.AddressZero
).callStatic.admin()
console.log(`admin currently set to ${admin}`)
if (getAddress(admin) !== getAddress(l2ProxyOwnerAddress)) { // ethers.Signer for the ddd. Should be the current owner of the Proxy.
// If the proxy admin isn't the l2ProxyOwnerAddress, we need to update it const dddSigner = await hre.ethers.provider.getSigner(deployer)
// We're assuming that the proxy admin is the ddd right now.
console.log('admin is not set to the l2ProxyOwnerAddress')
console.log(`Setting admin to ${l2ProxyOwnerAddress}`)
// ethers.Signer for the ddd // intended admin of the Proxy
const dddSigner = await hre.ethers.provider.getSigner(deployer) const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress
// change admin to the l2ProxyOwnerAddress // setup the Proxy contract with correct implementation and admin
const tx = await Proxy.connect(dddSigner).changeAdmin(l2ProxyOwnerAddress) await setupProxyContract(Proxy, dddSigner, {
const receipt = await tx.wait() targetImplAddress: Deployment__OptimistInviterImpl.address,
console.log(`admin set in ${receipt.transactionHash}`) targetProxyOwnerAddress: l2ProxyOwnerAddress,
} else { postUpgradeCallCalldata: initializeCalldata,
console.log('admin already set to proxy owner address') })
}
const Deployment__AttestationStation = await hre.deployments.get( const Deployment__AttestationStation = await hre.deployments.get(
'AttestationStationProxy' 'AttestationStationProxy'
...@@ -190,6 +137,6 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -190,6 +137,6 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
} }
deployFn.tags = ['OptimistInviterProxy', 'OptimistEnvironment'] deployFn.tags = ['OptimistInviterProxy', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistInviter'] deployFn.dependencies = ['AttestationStationProxy', 'OptimistInviterImpl']
export default deployFn export default deployFn
...@@ -5,8 +5,9 @@ import '@eth-optimism/hardhat-deploy-config' ...@@ -5,8 +5,9 @@ import '@eth-optimism/hardhat-deploy-config'
import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy' import 'hardhat-deploy'
import { assertContractVariable } from '@eth-optimism/contracts-bedrock/src/deploy-utils' import { assertContractVariable } from '@eth-optimism/contracts-bedrock/src/deploy-utils'
import { ethers, utils } from 'ethers' import { utils } from 'ethers'
import { setupProxyContract } from '../../src/helpers/setupProxyContract'
import type { DeployConfig } from '../../src' import type { DeployConfig } from '../../src'
const { getAddress } = utils const { getAddress } = utils
...@@ -21,7 +22,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -21,7 +22,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
throw new Error('Must deploy with the ddd') throw new Error('Must deploy with the ddd')
} }
const Deployment__Optimist = await hre.deployments.get('Optimist') const Deployment__OptimistImpl = await hre.deployments.get('Optimist')
console.log(`Deploying OptimistProxy with ${deployer}`) console.log(`Deploying OptimistProxy with ${deployer}`)
...@@ -48,69 +49,58 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -48,69 +49,58 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
Deployment__OptimistProxy.address Deployment__OptimistProxy.address
) )
const implementation = await Proxy.connect( // ethers.Signer for the ddd. Should be the current owner of the Proxy.
ethers.constants.AddressZero const dddSigner = await hre.ethers.provider.getSigner(deployer)
).callStatic.implementation()
console.log(`implementation set to ${implementation}`)
if (getAddress(implementation) !== getAddress(Deployment__Optimist.address)) {
console.log('implementation not set to Optimist contract')
console.log(`Setting implementation to ${Deployment__Optimist.address}`)
// Create the calldata for the call to `initialize()`
const name = deployConfig.optimistName
const symbol = deployConfig.optimistSymbol
const calldata = Optimist.interface.encodeFunctionData('initialize', [
name,
symbol,
])
const tx = await Proxy.upgradeToAndCall(
Deployment__Optimist.address,
calldata
)
const receipt = await tx.wait()
console.log(`implementation set in ${receipt.transactionHash}`)
} else {
console.log('implementation already set to Optimist contract')
}
// intended admin of the Proxy
const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress
const admin = await Proxy.connect(
ethers.constants.AddressZero
).callStatic.admin()
console.log(`admin set to ${admin}`)
if (getAddress(admin) !== getAddress(l2ProxyOwnerAddress)) {
console.log('detected admin is not set')
console.log(`Setting admin to ${l2ProxyOwnerAddress}`)
const tx = await Proxy.changeAdmin(l2ProxyOwnerAddress)
const receipt = await tx.wait()
console.log(`admin set in ${receipt.transactionHash}`)
} else {
console.log('admin already set to proxy owner address')
}
const Deployment__AttestationStation = await hre.deployments.get( // Create the calldata for the call to `initialize()`
const name = deployConfig.optimistName
const symbol = deployConfig.optimistSymbol
const initializeCalldata = Optimist.interface.encodeFunctionData(
'initialize',
[name, symbol]
)
// setup the Proxy contract with correct implementation and admin, and initialize atomically
await setupProxyContract(Proxy, dddSigner, {
targetImplAddress: Deployment__OptimistImpl.address,
targetProxyOwnerAddress: l2ProxyOwnerAddress,
postUpgradeCallCalldata: initializeCalldata,
})
const Deployment__AttestationStationProxy = await hre.deployments.get(
'AttestationStationProxy' 'AttestationStationProxy'
) )
const Deployment__OptimistAllowlistProxy = await hre.deployments.get(
'OptimistAllowlistProxy'
)
await assertContractVariable(Proxy, 'admin', l2ProxyOwnerAddress) await assertContractVariable(Proxy, 'admin', l2ProxyOwnerAddress)
await assertContractVariable(Optimist, 'name', deployConfig.optimistName) await assertContractVariable(Optimist, 'name', deployConfig.optimistName)
await assertContractVariable(Optimist, 'version', '1.0.0') await assertContractVariable(Optimist, 'version', '2.0.0')
await assertContractVariable(Optimist, 'symbol', deployConfig.optimistSymbol) await assertContractVariable(Optimist, 'symbol', deployConfig.optimistSymbol)
await assertContractVariable( await assertContractVariable(
Optimist, Optimist,
'ATTESTOR', 'BASE_URI_ATTESTOR',
deployConfig.attestorAddress deployConfig.optimistBaseUriAttestorAddress
)
await assertContractVariable(
Optimist,
'OPTIMIST_ALLOWLIST',
Deployment__OptimistAllowlistProxy.address
) )
await assertContractVariable( await assertContractVariable(
Optimist, Optimist,
'ATTESTATION_STATION', 'ATTESTATION_STATION',
Deployment__AttestationStation.address Deployment__AttestationStationProxy.address
) )
} }
deployFn.tags = ['OptimistProxy', 'OptimistEnvironment'] deployFn.tags = ['OptimistProxy', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'Optimist'] deployFn.dependencies = ['AttestationStationProxy', 'OptimistImpl']
export default deployFn export default deployFn
...@@ -31,7 +31,7 @@ export interface DeployConfig { ...@@ -31,7 +31,7 @@ export interface DeployConfig {
/** /**
* Address of the privileged attestor for the Optimist contract. * Address of the privileged attestor for the Optimist contract.
*/ */
attestorAddress: string optimistBaseUriAttestorAddress: string
/** /**
* Address of the privileged account for the OptimistInviter contract that can grant invites. * Address of the privileged account for the OptimistInviter contract that can grant invites.
...@@ -43,6 +43,17 @@ export interface DeployConfig { ...@@ -43,6 +43,17 @@ export interface DeployConfig {
*/ */
optimistInviterName: string optimistInviterName: string
/**
* Address of previleged account for the OptimistAllowlist contract that can add/remove people
* from allowlist.
*/
optimistAllowlistAllowlistAttestor: string
/**
* Address of Coinbase attestor that attests to whether someone has completed Quest.
*/
optimistAllowlistCoinbaseQuestAttestor: string
/** /**
* Address of the owner of the proxies on L2. There will be a ProxyAdmin deployed as a predeploy * Address of the owner of the proxies on L2. There will be a ProxyAdmin deployed as a predeploy
* after bedrock, so the owner of proxies should be updated to that after the upgrade. * after bedrock, so the owner of proxies should be updated to that after the upgrade.
...@@ -70,7 +81,7 @@ export const configSpec: DeployConfigSpec<DeployConfig> = { ...@@ -70,7 +81,7 @@ export const configSpec: DeployConfigSpec<DeployConfig> = {
type: 'string', type: 'string',
default: 'OPTIMIST', default: 'OPTIMIST',
}, },
attestorAddress: { optimistBaseUriAttestorAddress: {
type: 'address', type: 'address',
}, },
optimistInviterInviteGranter: { optimistInviterInviteGranter: {
...@@ -79,6 +90,15 @@ export const configSpec: DeployConfigSpec<DeployConfig> = { ...@@ -79,6 +90,15 @@ export const configSpec: DeployConfigSpec<DeployConfig> = {
optimistInviterName: { optimistInviterName: {
type: 'string', type: 'string',
}, },
optimistAllowlistAllowlistAttestor: {
type: 'address',
},
optimistAllowlistCoinbaseQuestAttestor: {
type: 'address',
},
l2ProxyOwnerAddress: { l2ProxyOwnerAddress: {
type: 'address', type: 'address',
}, },
......
import assert from 'assert'
import { ethers, utils } from 'ethers'
const { getAddress } = utils
type ProxyConfig = {
targetImplAddress: string
targetProxyOwnerAddress: string
postUpgradeCallCalldata?: string
}
// Sets up the newly deployed proxy contract such that:
// 1. The proxy's implementation is set to the target implementation
// 2. The proxy's admin is set to the target proxy owner
//
// If the values are set correctly already, it makes no transactions.
const setupProxyContract = async (
proxyContract: ethers.Contract,
signer: ethers.Signer,
{
targetImplAddress,
targetProxyOwnerAddress,
postUpgradeCallCalldata,
}: ProxyConfig
) => {
const currentAdmin = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.admin()
const signerAddress = await signer.getAddress()
// Gets the current implementation address the proxy is pointing to.
// callStatic is used since the `Proxy.implementation()` is not a view function and ethers will
// try to make a transaction if we don't use callStatic. Using the zero address as `from` lets us
// call functions on the proxy and not trigger the delegatecall. See Proxy.sol proxyCallIfNotAdmin
// modifier for more details.
const currentImplementation = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.implementation()
console.log(`implementation currently set to ${currentImplementation}`)
if (getAddress(currentImplementation) !== getAddress(targetImplAddress)) {
// If the proxy isn't pointing to the correct implementation, we need to set it to the correct
// one, then call initialize() in the proxy's context.
console.log('implementation not set to correct contract')
console.log(`Setting implementation to ${targetImplAddress}`)
// The signer needs to be the current admin, otherwise we don't have permission
// to update the implmentation or admin
assert(
signerAddress === currentAdmin,
'the passed signer is not the admin, cannot update implementation'
)
let tx: ethers.providers.TransactionResponse
if (!postUpgradeCallCalldata) {
console.log(
'postUpgradeCallCalldata is not provided. Using Proxy.upgrade()'
)
// Point the proxy to the target implementation
tx = await proxyContract.connect(signer).upgradeTo(targetImplAddress)
} else {
console.log(
'postUpgradeCallCalldata is provided. Using Proxy.upgradeAndCall()'
)
// Point the proxy to the target implementation,
// and call function in the proxy's context
tx = await proxyContract
.connect(signer)
.upgradeToAndCall(targetImplAddress, postUpgradeCallCalldata)
}
const receipt = await tx.wait()
console.log(`implementation set in ${receipt.transactionHash}`)
} else {
console.log(`implementation already set correctly to ${targetImplAddress}`)
}
console.log(`admin set to ${currentAdmin}`)
if (getAddress(currentAdmin) !== getAddress(targetProxyOwnerAddress)) {
// If the proxy admin isn't the l2ProxyOwnerAddress, we need to update it
// We're assuming that the proxy admin is the ddd right now.
console.log('detected admin is not set correctly')
console.log(`Setting admin to ${targetProxyOwnerAddress}`)
// The signer needs to be the current admin, otherwise we don't have permission
// to update the implmentation or admin
assert(
signerAddress === currentAdmin,
'proxyOwnerSigner is not the admin, cannot update admin'
)
// change admin to the l2ProxyOwnerAddress
const tx = await proxyContract
.connect(signer)
.changeAdmin(targetProxyOwnerAddress)
const receipt = await tx.wait()
console.log(`admin set in ${receipt.transactionHash}`)
} else {
console.log(`admin already set correctly to ${targetProxyOwnerAddress}`)
}
const updatedImplementation = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.implementation()
const updatedAdmin = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.admin()
assert(
getAddress(updatedAdmin) === getAddress(targetProxyOwnerAddress),
'Something went wrong - admin not set correctly after transaction'
)
assert(
getAddress(updatedImplementation) === getAddress(targetImplAddress),
'Something went wrong - implementation not set correctly after transaction'
)
console.log(
`Proxy at ${proxyContract.address} is set up with implementation: ${updatedImplementation} and admin: ${updatedAdmin}`
)
}
export { setupProxyContract }
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