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:
name: run tests
command: |
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>>
- run:
name: upload coverage
......@@ -651,9 +651,10 @@ jobs:
command: |
# Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional
# constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building.
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=true OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \
# 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 \
-- -timeout=20m ./...
-- -timeout=20m -parallel=8 ./...
working_directory: <<parameters.module>>
- store_test_results:
path: /tmp/test-results
......
......@@ -384,7 +384,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
}
// 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
// (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
......@@ -428,7 +428,7 @@ func TestBigL2Txs(gt *testing.T) {
}
// 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)
status := sequencer.SyncStatus()
// 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{
}
func TestMigration(t *testing.T) {
parallel(t)
if !config.enabled {
t.Skipf("skipping migration tests")
return
......
......@@ -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.
func TestMissingGasLimit(t *testing.T) {
parallel(t)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
......@@ -42,6 +43,7 @@ func TestMissingGasLimit(t *testing.T) {
// TestInvalidDepositInFCU runs an invalid deposit through a FCU/GetPayload/NewPayload/FCU set of calls.
// This tests that deposits must always allow the block to be built even if they are invalid.
func TestInvalidDepositInFCU(t *testing.T) {
parallel(t)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
......@@ -76,6 +78,7 @@ func TestInvalidDepositInFCU(t *testing.T) {
}
func TestPreregolith(t *testing.T) {
parallel(t)
futureTimestamp := hexutil.Uint64(4)
tests := []struct {
name string
......@@ -255,6 +258,7 @@ func TestPreregolith(t *testing.T) {
}
func TestRegolith(t *testing.T) {
parallel(t)
tests := []struct {
name string
regolithTime hexutil.Uint64
......
......@@ -425,6 +425,8 @@ func TestMixedWithdrawalValidity(t *testing.T) {
for i := 0; i <= 8; i++ {
i := i // avoid loop var capture
t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) {
parallel(t)
// Create our system configuration, funding all accounts we created for L1/L2, and start it
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 6
......
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 (
"encoding/binary"
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/beacon/engine"
"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/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum-optimism/optimism/op-node/eth"
)
type EngineBackend interface {
CurrentBlock() *types.Header
CurrentSafeBlock() *types.Header
CurrentFinalBlock() *types.Header
GetHeaderByHash(hash common.Hash) *types.Header
GetBlockByHash(hash common.Hash) *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
GetCanonicalHash(n uint64) common.Hash
......@@ -47,6 +39,8 @@ type EngineBackend interface {
SetCanonical(head *types.Block) (common.Hash, error)
SetFinalized(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.
......@@ -57,12 +51,8 @@ type L2EngineAPI struct {
backend EngineBackend
// L2 block building data
l2BuildingHeader *types.Header // block header that we add txs to for block building
l2BuildingState *state.StateDB // state used for block building
l2GasPool *core.GasPool // track gas used of ongoing building
blockProcessor *BlockProcessor
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.
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
......@@ -102,7 +92,10 @@ func computePayloadId(headBlockHash common.Hash, params *eth.PayloadAttributes)
}
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 {
......@@ -115,12 +108,10 @@ func (ea *L2EngineAPI) PendingIndices(from common.Address) uint64 {
var (
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 {
if ea.l2BuildingHeader == nil {
if ea.blockProcessor == nil {
return ErrNotBuildingBlock
}
if ea.l2ForceEmpty {
......@@ -129,60 +120,32 @@ func (ea *L2EngineAPI) IncludeTx(tx *types.Transaction, from common.Address) err
return nil
}
if tx.Gas() > ea.l2BuildingHeader.GasLimit {
return fmt.Errorf("%w tx gas: %d, block gas limit: %d", ErrExceedsGasLimit, tx.Gas(), ea.l2BuildingHeader.GasLimit)
}
if tx.Gas() > uint64(*ea.l2GasPool) {
return fmt.Errorf("%w: %d, only have %d", ErrUsesTooMuchGas, tx.Gas(), uint64(*ea.l2GasPool))
err := ea.blockProcessor.CheckTxWithinGasLimit(tx)
if err != nil {
return err
}
ea.pendingIndices[from] = ea.pendingIndices[from] + 1 // won't retry the tx
ea.l2BuildingState.SetTxContext(tx.Hash(), len(ea.l2Transactions))
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())
err = ea.blockProcessor.AddTx(tx)
if err != nil {
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
}
func (ea *L2EngineAPI) startBlock(parent common.Hash, params *eth.PayloadAttributes) error {
if ea.l2BuildingHeader != nil {
ea.log.Warn("started building new block without ending previous block", "previous", ea.l2BuildingHeader, "prev_payload_id", ea.payloadID)
if ea.blockProcessor != nil {
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)
if parentHeader == nil {
return fmt.Errorf("uknown parent block: %s", parent)
}
statedb, err := ea.backend.StateAt(parentHeader.Root)
processor, err := NewBlockProcessorFromPayloadAttributes(ea.backend, parent, params)
if err != nil {
return fmt.Errorf("failed to init state db around block %s (state %s): %w", parent, parentHeader.Root, 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),
return err
}
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.blockProcessor = processor
ea.pendingIndices = make(map[common.Address]uint64)
ea.l2ForceEmpty = params.NoTxPool
ea.l2GasPool = new(core.GasPool).AddGas(header.GasLimit)
ea.payloadID = computePayloadId(parent, params)
// pre-process the deposits
......@@ -191,29 +154,26 @@ func (ea *L2EngineAPI) startBlock(parent common.Hash, params *eth.PayloadAttribu
if err := tx.UnmarshalBinary(otx); err != nil {
return fmt.Errorf("transaction %d is not valid: %w", i, err)
}
ea.l2BuildingState.SetTxContext(tx.Hash(), i)
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())
err := ea.blockProcessor.AddTx(&tx)
if err != nil {
ea.l2TxFailed = append(ea.l2TxFailed, &tx)
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
}
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)
}
header := ea.l2BuildingHeader
ea.l2BuildingHeader = nil
processor := ea.blockProcessor
ea.blockProcessor = nil
header.GasUsed = header.GasLimit - uint64(*ea.l2GasPool)
header.Root = ea.l2BuildingState.IntermediateRoot(ea.backend.Config().IsEIP158(header.Number))
block := types.NewBlock(header, ea.l2Transactions, nil, ea.l2Receipts, trie.NewStackTrie(nil))
block, err := processor.Assemble()
if err != nil {
return nil, fmt.Errorf("assemble block: %w", err)
}
return block, nil
}
......
......@@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
......@@ -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")
imported := api.backend.GetBlockByHash(block.BlockHash)
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) {
......@@ -98,9 +131,14 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
api := newTestHelper(t, createBackend)
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)
// 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)
api.assert.NoError(err)
api.assert.Equal(eth.ExecutionInvalid, r.Status)
......@@ -110,14 +148,81 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
api := newTestHelper(t, createBackend)
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)
// 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)
api.assert.NoError(err)
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) {
api := newTestHelper(t, createBackend)
......
......@@ -5,9 +5,11 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '',
optimistName: '',
optimistSymbol: '',
attestorAddress: '',
optimistInviterName: '',
optimistBaseUriAttestorAddress: '',
optimistInviterInviteGranter: '',
optimistInviterName: '',
optimistAllowlistAllowlistAttestor: '',
optimistAllowlistCoinbaseQuestAttestor: '',
}
export default config
......@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistBaseUriAttestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistAllowlistCoinbaseQuestAttestor:
'0x661B7Acca8ebd93AFd349a088e9a9A00053DB1BF',
}
export default config
......@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistName: 'OP Citizenship',
optimistSymbol: 'OPNFT',
attestorAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistBaseUriAttestorAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistInviterInviteGranter: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistAllowlistCoinbaseQuestAttestor:
'0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
}
export default config
import config from './hardhat'
// uses the same config as hardhat.ts
export default config
......@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistBaseUriAttestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistAllowlistCoinbaseQuestAttestor:
'0x661B7Acca8ebd93AFd349a088e9a9A00053DB1BF',
}
export default config
......@@ -2,12 +2,17 @@ import { DeployConfig } from '../../src'
const config: DeployConfig = {
ddd: '0x9C6373dE60c2D3297b18A8f964618ac46E011B58',
// EcoPod test account
l2ProxyOwnerAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST',
attestorAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistBaseUriAttestorAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistInviterInviteGranter: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistAllowlistCoinbaseQuestAttestor:
'0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
}
export default config
......@@ -5,9 +5,13 @@ const config: DeployConfig = {
l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistBaseUriAttestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter',
optimistAllowlistAllowlistAttestor:
'0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistAllowlistCoinbaseQuestAttestor:
'0x661B7Acca8ebd93AFd349a088e9a9A00053DB1BF',
}
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) => {
console.log(`Deploying Optimist implementation with ${deployer}`)
const Deployment__AttestationStation = await hre.deployments.get(
const Deployment__AttestationStationProxy = await hre.deployments.get(
'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`)
console.log(`Using ${deployConfig.attestorAddress} as ATTESTOR`)
const Deployment__OptimistAllowlistProxy = await hre.deployments.get(
'OptimistAllowlistProxy'
)
const optimistAllowlistAddress = Deployment__OptimistAllowlistProxy.address
const { deploy } = await hre.deployments.deterministic('Optimist', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['Optimist']),
......@@ -28,8 +34,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
args: [
deployConfig.optimistName,
deployConfig.optimistSymbol,
deployConfig.attestorAddress,
deployConfig.optimistBaseUriAttestorAddress,
attestationStationAddress,
optimistAllowlistAddress,
],
log: true,
})
......@@ -37,6 +44,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
await deploy()
}
deployFn.tags = ['Optimist', 'OptimistEnvironment']
deployFn.tags = ['OptimistImpl', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistAllowlistProxy']
export default deployFn
......@@ -37,7 +37,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
await deploy()
}
deployFn.tags = ['OptimistInviter', 'OptimistEnvironment']
deployFn.tags = ['OptimistInviterImpl', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy']
export default deployFn
......@@ -10,6 +10,7 @@ import { assertContractVariable } from '@eth-optimism/contracts-bedrock/src/depl
import { ethers, utils } from 'ethers'
import type { DeployConfig } from '../../src'
import { setupProxyContract } from '../../src/helpers/setupProxyContract'
const { getAddress } = utils
......@@ -73,7 +74,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
'OptimistInviterProxy'
)
console.log(
`OptimistProxy deployed to ${Deployment__OptimistInviterProxy.address}`
`OptimistInviterProxy deployed to ${Deployment__OptimistInviterProxy.address}`
)
// Deployed Proxy.sol contract
......@@ -88,79 +89,25 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
Deployment__OptimistInviterProxy.address
)
// 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 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(
const initializeCalldata = OptimistInviter.interface.encodeFunctionData(
'initialize',
[name]
)
// ethers.Signer for the ddd
// ethers.Signer for the ddd. Should be the current owner of the Proxy.
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'
)
}
// intended admin of the Proxy
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)) {
// 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('admin is not set to the l2ProxyOwnerAddress')
console.log(`Setting admin to ${l2ProxyOwnerAddress}`)
// ethers.Signer for the ddd
const dddSigner = await hre.ethers.provider.getSigner(deployer)
// change admin to the l2ProxyOwnerAddress
const tx = await Proxy.connect(dddSigner).changeAdmin(l2ProxyOwnerAddress)
const receipt = await tx.wait()
console.log(`admin set in ${receipt.transactionHash}`)
} else {
console.log('admin already set to proxy owner address')
}
// setup the Proxy contract with correct implementation and admin
await setupProxyContract(Proxy, dddSigner, {
targetImplAddress: Deployment__OptimistInviterImpl.address,
targetProxyOwnerAddress: l2ProxyOwnerAddress,
postUpgradeCallCalldata: initializeCalldata,
})
const Deployment__AttestationStation = await hre.deployments.get(
'AttestationStationProxy'
......@@ -190,6 +137,6 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
}
deployFn.tags = ['OptimistInviterProxy', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistInviter']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistInviterImpl']
export default deployFn
......@@ -5,8 +5,9 @@ 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 { utils } from 'ethers'
import { setupProxyContract } from '../../src/helpers/setupProxyContract'
import type { DeployConfig } from '../../src'
const { getAddress } = utils
......@@ -21,7 +22,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
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}`)
......@@ -48,69 +49,58 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
Deployment__OptimistProxy.address
)
const implementation = await Proxy.connect(
ethers.constants.AddressZero
).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}`)
// 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
// 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 initializeCalldata = Optimist.interface.encodeFunctionData(
'initialize',
[name, symbol]
)
const receipt = await tx.wait()
console.log(`implementation set in ${receipt.transactionHash}`)
} else {
console.log('implementation already set to Optimist contract')
}
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')
}
// 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__AttestationStation = await hre.deployments.get(
const Deployment__AttestationStationProxy = await hre.deployments.get(
'AttestationStationProxy'
)
const Deployment__OptimistAllowlistProxy = await hre.deployments.get(
'OptimistAllowlistProxy'
)
await assertContractVariable(Proxy, 'admin', l2ProxyOwnerAddress)
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,
'ATTESTOR',
deployConfig.attestorAddress
'BASE_URI_ATTESTOR',
deployConfig.optimistBaseUriAttestorAddress
)
await assertContractVariable(
Optimist,
'OPTIMIST_ALLOWLIST',
Deployment__OptimistAllowlistProxy.address
)
await assertContractVariable(
Optimist,
'ATTESTATION_STATION',
Deployment__AttestationStation.address
Deployment__AttestationStationProxy.address
)
}
deployFn.tags = ['OptimistProxy', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'Optimist']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistImpl']
export default deployFn
......@@ -31,7 +31,7 @@ export interface DeployConfig {
/**
* 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.
......@@ -43,6 +43,17 @@ export interface DeployConfig {
*/
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
* after bedrock, so the owner of proxies should be updated to that after the upgrade.
......@@ -70,7 +81,7 @@ export const configSpec: DeployConfigSpec<DeployConfig> = {
type: 'string',
default: 'OPTIMIST',
},
attestorAddress: {
optimistBaseUriAttestorAddress: {
type: 'address',
},
optimistInviterInviteGranter: {
......@@ -79,6 +90,15 @@ export const configSpec: DeployConfigSpec<DeployConfig> = {
optimistInviterName: {
type: 'string',
},
optimistAllowlistAllowlistAttestor: {
type: 'address',
},
optimistAllowlistCoinbaseQuestAttestor: {
type: 'address',
},
l2ProxyOwnerAddress: {
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