Commit bcdf0d12 authored by Adrian Sutton's avatar Adrian Sutton

Merge branch 'develop' into aj/rearrange-fault-proof-e2e

parents 48f0628e 363d188f
...@@ -346,7 +346,7 @@ jobs: ...@@ -346,7 +346,7 @@ jobs:
contracts-bedrock-tests: contracts-bedrock-tests:
docker: docker:
- image: <<pipeline.parameters.ci_builder_image>> - image: <<pipeline.parameters.ci_builder_image>>
resource_class: medium resource_class: xlarge
steps: steps:
- checkout - checkout
- run: - run:
...@@ -378,7 +378,7 @@ jobs: ...@@ -378,7 +378,7 @@ jobs:
contracts-bedrock-checks: contracts-bedrock-checks:
docker: docker:
- image: <<pipeline.parameters.ci_builder_image>> - image: <<pipeline.parameters.ci_builder_image>>
resource_class: medium resource_class: xlarge
steps: steps:
- checkout - checkout
- run: - run:
...@@ -432,6 +432,16 @@ jobs: ...@@ -432,6 +432,16 @@ jobs:
pnpm autogen:invariant-docs pnpm autogen:invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV" git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run:
name: abi snapshot
command: |
pnpm abi-snapshot
if [ "$(git diff --exit-code snapshots)" ]; then
[ -z "$(git ls-files --others --exclude-standard snapshots)" ] || echo "export ABI_SNAPSHOT_STATUS=1" >> "$BASH_ENV"
else
echo "export ABI_SNAPSHOT_STATUS=1" >> "$BASH_ENV"
fi
working_directory: packages/contracts-bedrock
- run: - run:
name: check statuses name: check statuses
command: | command: |
...@@ -459,6 +469,10 @@ jobs: ...@@ -459,6 +469,10 @@ jobs:
echo "Deploy config check failed, see job output for details." echo "Deploy config check failed, see job output for details."
FAILED=1 FAILED=1
fi fi
if [[ "$ABI_SNAPSHOT_STATUS" -ne 0 ]]; then
echo "ABI snapshot check failed, see job output for details."
FAILED=1
fi
if [[ "$FAILED" -ne 0 ]]; then if [[ "$FAILED" -ne 0 ]]; then
exit 1 exit 1
fi fi
......
...@@ -61,7 +61,7 @@ type BridgeMessagesDB interface { ...@@ -61,7 +61,7 @@ type BridgeMessagesDB interface {
StoreL2BridgeMessages([]L2BridgeMessage) error StoreL2BridgeMessages([]L2BridgeMessage) error
MarkRelayedL2BridgeMessage(common.Hash, uuid.UUID) error MarkRelayedL2BridgeMessage(common.Hash, uuid.UUID) error
StoreL2BridgeMessageV1MessageHash(common.Hash, common.Hash) error StoreL2BridgeMessageV1MessageHashes([]L2BridgeMessageVersionedMessageHash) error
} }
/** /**
...@@ -141,15 +141,11 @@ func (db bridgeMessagesDB) StoreL2BridgeMessages(messages []L2BridgeMessage) err ...@@ -141,15 +141,11 @@ func (db bridgeMessagesDB) StoreL2BridgeMessages(messages []L2BridgeMessage) err
return result.Error return result.Error
} }
func (db bridgeMessagesDB) StoreL2BridgeMessageV1MessageHash(msgHash, v1MsgHash common.Hash) error { func (db bridgeMessagesDB) StoreL2BridgeMessageV1MessageHashes(versionedHashes []L2BridgeMessageVersionedMessageHash) error {
if msgHash == v1MsgHash {
return fmt.Errorf("message hash is equal to the v1 message: %s", msgHash)
}
deduped := db.gorm.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "message_hash"}}, DoNothing: true}) deduped := db.gorm.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "message_hash"}}, DoNothing: true})
result := deduped.Create(&L2BridgeMessageVersionedMessageHash{MessageHash: msgHash, V1MessageHash: v1MsgHash}) result := deduped.Create(&versionedHashes)
if result.Error == nil && int(result.RowsAffected) < 1 { if result.Error == nil && int(result.RowsAffected) < len(versionedHashes) {
db.log.Warn("ignored L2 bridge v1 message hash duplicates") db.log.Warn("ignored L2 bridge v1 message hash duplicates", "duplicates", len(versionedHashes)-int(result.RowsAffected))
} }
return result.Error return result.Error
......
...@@ -13,8 +13,10 @@ import ( ...@@ -13,8 +13,10 @@ import (
"github.com/ethereum-optimism/optimism/indexer/client" "github.com/ethereum-optimism/optimism/indexer/client"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/prometheus/client_golang/prometheus"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -31,6 +33,7 @@ import ( ...@@ -31,6 +33,7 @@ import (
type E2ETestSuite struct { type E2ETestSuite struct {
t *testing.T t *testing.T
MetricsRegistry *prometheus.Registry
// API // API
Client *client.Client Client *client.Client
...@@ -153,6 +156,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -153,6 +156,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
return E2ETestSuite{ return E2ETestSuite{
t: t, t: t,
MetricsRegistry: metrics.NewRegistry(),
Client: client, Client: client,
DB: ix.DB, DB: ix.DB,
Indexer: ix, Indexer: ix,
......
...@@ -312,7 +312,7 @@ func (b *BridgeProcessor) processFinalizedL1Events() error { ...@@ -312,7 +312,7 @@ func (b *BridgeProcessor) processFinalizedL1Events() error {
legacyBridgeLog := l1BridgeLog.New("mode", "legacy", "from_block_number", legacyFromL1Height, "to_block_number", legacyToL1Height) legacyBridgeLog := l1BridgeLog.New("mode", "legacy", "from_block_number", legacyFromL1Height, "to_block_number", legacyToL1Height)
legacyBridgeLog.Info("scanning for finalized bridge events") legacyBridgeLog.Info("scanning for finalized bridge events")
if err := bridge.LegacyL1ProcessFinalizedBridgeEvents(legacyBridgeLog, tx, b.metrics, b.l1Etl.EthClient, b.chainConfig.L1Contracts, legacyFromL1Height, legacyToL1Height); err != nil { if err := bridge.LegacyL1ProcessFinalizedBridgeEvents(legacyBridgeLog, tx, b.metrics, b.chainConfig.L1Contracts, legacyFromL1Height, legacyToL1Height); err != nil {
return err return err
} else if legacyToL1Height.Cmp(toL1Height) == 0 { } else if legacyToL1Height.Cmp(toL1Height) == 0 {
return nil // a-ok! Entire range was legacy blocks return nil // a-ok! Entire range was legacy blocks
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/indexer/bigint" "github.com/ethereum-optimism/optimism/indexer/bigint"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/processors/bridge/ovm1"
"github.com/ethereum-optimism/optimism/indexer/processors/contracts" "github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -155,21 +156,30 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -155,21 +156,30 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected proven withdrawals", "size", len(provenWithdrawals)) log.Info("detected proven withdrawals", "size", len(provenWithdrawals))
} }
skippedOVM1ProvenWithdrawals := 0
for i := range provenWithdrawals { for i := range provenWithdrawals {
proven := provenWithdrawals[i] provenWithdrawal := provenWithdrawals[i]
withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(proven.WithdrawalHash) withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(provenWithdrawal.WithdrawalHash)
if err != nil { if err != nil {
return err return err
} else if withdrawal == nil { } else if withdrawal == nil {
return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", proven.Event.TransactionHash) if _, ok := ovm1.PortalWithdrawalTransactions[provenWithdrawal.WithdrawalHash]; ok {
skippedOVM1ProvenWithdrawals++
continue
}
return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", provenWithdrawal.Event.TransactionHash)
} }
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(proven.WithdrawalHash, provenWithdrawals[i].Event.GUID); err != nil { if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(provenWithdrawal.WithdrawalHash, provenWithdrawals[i].Event.GUID); err != nil {
return fmt.Errorf("failed to mark withdrawal as proven. tx_hash = %s: %w", proven.Event.TransactionHash, err) return fmt.Errorf("failed to mark withdrawal as proven. tx_hash = %s: %w", provenWithdrawal.Event.TransactionHash, err)
} }
} }
if len(provenWithdrawals) > 0 { if len(provenWithdrawals) > 0 {
metrics.RecordL1ProvenWithdrawals(len(provenWithdrawals)) metrics.RecordL1ProvenWithdrawals(len(provenWithdrawals))
if skippedOVM1ProvenWithdrawals > 0 {
metrics.RecordL1SkippedOVM1ProvenWithdrawals(skippedOVM1ProvenWithdrawals)
log.Info("skipped OVM 1.0 proven withdrawals", "size", skippedOVM1ProvenWithdrawals)
}
} }
// (2) OptimismPortal (finalized withdrawals) // (2) OptimismPortal (finalized withdrawals)
...@@ -181,12 +191,17 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -181,12 +191,17 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected finalized withdrawals", "size", len(finalizedWithdrawals)) log.Info("detected finalized withdrawals", "size", len(finalizedWithdrawals))
} }
skippedOVM1FinalizedWithdrawals := 0
for i := range finalizedWithdrawals { for i := range finalizedWithdrawals {
finalizedWithdrawal := finalizedWithdrawals[i] finalizedWithdrawal := finalizedWithdrawals[i]
withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(finalizedWithdrawal.WithdrawalHash) withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(finalizedWithdrawal.WithdrawalHash)
if err != nil { if err != nil {
return err return err
} else if withdrawal == nil { } else if withdrawal == nil {
if _, ok := ovm1.PortalWithdrawalTransactions[finalizedWithdrawal.WithdrawalHash]; ok {
skippedOVM1FinalizedWithdrawals++
continue
}
return fmt.Errorf("missing indexed withdrawal on finalization! tx_hash = %s", finalizedWithdrawal.Event.TransactionHash.String()) return fmt.Errorf("missing indexed withdrawal on finalization! tx_hash = %s", finalizedWithdrawal.Event.TransactionHash.String())
} }
...@@ -196,6 +211,10 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -196,6 +211,10 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
} }
if len(finalizedWithdrawals) > 0 { if len(finalizedWithdrawals) > 0 {
metrics.RecordL1FinalizedWithdrawals(len(finalizedWithdrawals)) metrics.RecordL1FinalizedWithdrawals(len(finalizedWithdrawals))
if skippedOVM1ProvenWithdrawals > 0 {
metrics.RecordL1SkippedOVM1FinalizedWithdrawals(skippedOVM1FinalizedWithdrawals)
log.Info("skipped OVM 1.0 finalized withdrawals", "size", skippedOVM1FinalizedWithdrawals)
}
} }
// (3) L1CrossDomainMessenger // (3) L1CrossDomainMessenger
...@@ -207,21 +226,30 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M ...@@ -207,21 +226,30 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages)) log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
} }
skippedOVM1Messages := 0
for i := range crossDomainRelayedMessages { for i := range crossDomainRelayedMessages {
relayed := crossDomainRelayedMessages[i] relayedMessage := crossDomainRelayedMessages[i]
message, err := db.BridgeMessages.L2BridgeMessage(relayed.MessageHash) message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash)
if err != nil { if err != nil {
return err return err
} else if message == nil { } else if message == nil {
return fmt.Errorf("missing indexed L2CrossDomainMessager message! tx_hash = %s", relayed.Event.TransactionHash.String()) if _, ok := ovm1.L1RelayedMessages[relayedMessage.MessageHash]; ok {
skippedOVM1Messages++
continue
}
return fmt.Errorf("missing indexed L2CrossDomainMessager message! tx_hash = %s", relayedMessage.Event.TransactionHash.String())
} }
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayed.MessageHash, relayed.Event.GUID); err != nil { if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayed.Event.TransactionHash, err) return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
} }
} }
if len(crossDomainRelayedMessages) > 0 { if len(crossDomainRelayedMessages) > 0 {
metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages)) metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
if skippedOVM1Messages > 0 { // Logged as a warning just for visibility
metrics.RecordL1SkippedOVM1CrossDomainRelayedMessages(skippedOVM1Messages)
log.Info("skipped OVM 1.0 relayed L2CrossDomainMessenger withdrawals", "size", skippedOVM1Messages)
}
} }
// (4) L1StandardBridge // (4) L1StandardBridge
......
...@@ -10,8 +10,9 @@ import ( ...@@ -10,8 +10,9 @@ import (
"github.com/ethereum-optimism/optimism/indexer/bigint" "github.com/ethereum-optimism/optimism/indexer/bigint"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/processors/bridge/ovm1"
"github.com/ethereum-optimism/optimism/indexer/processors/contracts" "github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
) )
...@@ -165,6 +166,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -165,6 +166,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
withdrawnWEI := bigint.Zero withdrawnWEI := bigint.Zero
sentMessages := make(map[logKey]sentMessageEvent, len(crossDomainSentMessages)) sentMessages := make(map[logKey]sentMessageEvent, len(crossDomainSentMessages))
bridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages)) bridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages))
versionedMessageHashes := make([]database.L2BridgeMessageVersionedMessageHash, len(crossDomainSentMessages))
transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(crossDomainSentMessages)) transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(crossDomainSentMessages))
for i := range crossDomainSentMessages { for i := range crossDomainSentMessages {
sentMessage := crossDomainSentMessages[i] sentMessage := crossDomainSentMessages[i]
...@@ -172,15 +174,14 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -172,15 +174,14 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
// Since these message can be relayed in bedrock, we utilize the migrated withdrawal hash // Since these message can be relayed in bedrock, we utilize the migrated withdrawal hash
// and also store the v1 version of the message hash such that the bedrock l1 finalization // and also store the v1 version of the message hash such that the bedrock l1 finalization
// processor works as expected // processor works as expected. There will be some entries relayed pre-bedrock that will not
// require the entry but we'll store it anyways as a large % of these withdrawals are not relayed
// pre-bedrock.
v1MessageHash, err := legacyBridgeMessageV1MessageHash(&sentMessage.BridgeMessage) v1MessageHash, err := legacyBridgeMessageV1MessageHash(&sentMessage.BridgeMessage)
if err != nil { if err != nil {
return fmt.Errorf("failed to compute versioned message hash: %w", err) return fmt.Errorf("failed to compute versioned message hash: %w", err)
} }
if err := db.BridgeMessages.StoreL2BridgeMessageV1MessageHash(sentMessage.BridgeMessage.MessageHash, v1MessageHash); err != nil {
return err
}
withdrawalHash, err := legacyBridgeMessageWithdrawalHash(preset, &sentMessage.BridgeMessage) withdrawalHash, err := legacyBridgeMessageWithdrawalHash(preset, &sentMessage.BridgeMessage)
if err != nil { if err != nil {
...@@ -202,10 +203,8 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -202,10 +203,8 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
} }
sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = sentMessageEvent{&sentMessage, withdrawalHash} sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = sentMessageEvent{&sentMessage, withdrawalHash}
bridgeMessages[i] = database.L2BridgeMessage{ bridgeMessages[i] = database.L2BridgeMessage{TransactionWithdrawalHash: withdrawalHash, BridgeMessage: sentMessage.BridgeMessage}
TransactionWithdrawalHash: sentMessage.BridgeMessage.MessageHash, versionedMessageHashes[i] = database.L2BridgeMessageVersionedMessageHash{MessageHash: sentMessage.BridgeMessage.MessageHash, V1MessageHash: v1MessageHash}
BridgeMessage: sentMessage.BridgeMessage,
}
} }
if len(bridgeMessages) > 0 { if len(bridgeMessages) > 0 {
if err := db.BridgeTransactions.StoreL2TransactionWithdrawals(transactionWithdrawals); err != nil { if err := db.BridgeTransactions.StoreL2TransactionWithdrawals(transactionWithdrawals); err != nil {
...@@ -214,6 +213,9 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -214,6 +213,9 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
if err := db.BridgeMessages.StoreL2BridgeMessages(bridgeMessages); err != nil { if err := db.BridgeMessages.StoreL2BridgeMessages(bridgeMessages); err != nil {
return err return err
} }
if err := db.BridgeMessages.StoreL2BridgeMessageV1MessageHashes(versionedMessageHashes); err != nil {
return err
}
withdrawnETH, _ := bigint.WeiToETH(withdrawnWEI).Float64() withdrawnETH, _ := bigint.WeiToETH(withdrawnWEI).Float64()
metrics.RecordL2TransactionWithdrawals(len(transactionWithdrawals), withdrawnETH) metrics.RecordL2TransactionWithdrawals(len(transactionWithdrawals), withdrawnETH)
...@@ -270,7 +272,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -270,7 +272,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
// according to the pre-bedrock protocol. This follows: // according to the pre-bedrock protocol. This follows:
// 1. L1CrossDomainMessenger // 1. L1CrossDomainMessenger
// 2. L1StandardBridge // 2. L1StandardBridge
func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Client node.EthClient, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error { func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
// (1) L1CrossDomainMessenger -> This is the root-most contract from which bridge events are finalized since withdrawals must be initiated from the // (1) L1CrossDomainMessenger -> This is the root-most contract from which bridge events are finalized since withdrawals must be initiated from the
// L2CrossDomainMessenger. Since there's no two-step withdrawal process, we mark the transaction as proven/finalized in the same step // L2CrossDomainMessenger. Since there's no two-step withdrawal process, we mark the transaction as proven/finalized in the same step
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight) crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
...@@ -281,38 +283,19 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -281,38 +283,19 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages)) log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
} }
skippedPreRegenesisMessages := 0 skippedOVM1Messages := 0
for i := range crossDomainRelayedMessages { for i := range crossDomainRelayedMessages {
relayedMessage := crossDomainRelayedMessages[i] relayedMessage := crossDomainRelayedMessages[i]
message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash) message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash)
if err != nil { if err != nil {
return err return err
} else if message == nil { } else if message == nil {
// Before surfacing an error about a missing withdrawal, we need to handle an edge case if _, ok := ovm1.L1RelayedMessages[relayedMessage.MessageHash]; ok {
// for OP-Mainnet pre-regensis withdrawals that no longer exist on L2. skippedOVM1Messages++
tx, err := l1Client.TxByHash(relayedMessage.Event.TransactionHash)
if err != nil {
return fmt.Errorf("unable to query legacy relayed. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
} else if tx == nil {
return fmt.Errorf("missing tx for relayed message! tx_hash = %s", relayedMessage.Event.TransactionHash)
}
relayMessageData := tx.Data()[4:]
inputs, err := contracts.CrossDomainMessengerLegacyRelayMessageEncoding.Inputs.Unpack(relayMessageData)
if err != nil || inputs == nil {
return fmt.Errorf("unable to extract XDomainCallData from relayMessage transaction. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
}
// NOTE: Since OP-Mainnet is the only network to go through a regensis we can simply harcode the
// the starting message nonce at genesis (100k). Any relayed withdrawal on L1 with a lesser nonce
// is a clear indicator of a pre-regenesis withdrawal.
if inputs[3].(*big.Int).Int64() < 100_000 {
// skip pre-regenesis withdrawals
skippedPreRegenesisMessages++
continue continue
} else {
return fmt.Errorf("missing indexed L2CrossDomainMessenger message! tx_hash = %s", relayedMessage.Event.TransactionHash)
} }
return fmt.Errorf("missing indexed L2CrossDomainMessenger message! tx_hash %s", relayedMessage.Event.TransactionHash.String())
} }
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil { if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
...@@ -328,13 +311,11 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri ...@@ -328,13 +311,11 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
} }
} }
if len(crossDomainRelayedMessages) > 0 { if len(crossDomainRelayedMessages) > 0 {
metrics.RecordL1ProvenWithdrawals(len(crossDomainRelayedMessages))
metrics.RecordL1FinalizedWithdrawals(len(crossDomainRelayedMessages))
metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages)) metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
if skippedOVM1Messages > 0 { // Logged as a warning just for visibility
metrics.RecordL1SkippedOVM1CrossDomainRelayedMessages(skippedOVM1Messages)
log.Info("skipped OVM 1.0 relayed L2CrossDomainMessenger withdrawals", "size", skippedOVM1Messages)
} }
if skippedPreRegenesisMessages > 0 {
// Logged as a warning just for visibility
log.Warn("skipped pre-regensis relayed L2CrossDomainMessenger withdrawals", "size", skippedPreRegenesisMessages)
} }
// (2) L1StandardBridge // (2) L1StandardBridge
......
...@@ -25,6 +25,10 @@ type L1Metricer interface { ...@@ -25,6 +25,10 @@ type L1Metricer interface {
RecordL1CrossDomainSentMessages(size int) RecordL1CrossDomainSentMessages(size int)
RecordL1CrossDomainRelayedMessages(size int) RecordL1CrossDomainRelayedMessages(size int)
RecordL1SkippedOVM1ProvenWithdrawals(size int)
RecordL1SkippedOVM1FinalizedWithdrawals(size int)
RecordL1SkippedOVM1CrossDomainRelayedMessages(size int)
RecordL1InitiatedBridgeTransfers(token common.Address, size int) RecordL1InitiatedBridgeTransfers(token common.Address, size int)
RecordL1FinalizedBridgeTransfers(token common.Address, size int) RecordL1FinalizedBridgeTransfers(token common.Address, size int)
} }
...@@ -56,15 +60,19 @@ type bridgeMetrics struct { ...@@ -56,15 +60,19 @@ type bridgeMetrics struct {
intervalFailures *prometheus.CounterVec intervalFailures *prometheus.CounterVec
txDeposits prometheus.Counter txDeposits prometheus.Counter
txMintedETH prometheus.Counter
txWithdrawals prometheus.Counter txWithdrawals prometheus.Counter
txWithdrawnETH prometheus.Counter
provenWithdrawals prometheus.Counter provenWithdrawals prometheus.Counter
finalizedWithdrawals prometheus.Counter finalizedWithdrawals prometheus.Counter
txMintedETH prometheus.Counter
txWithdrawnETH prometheus.Counter
sentMessages *prometheus.CounterVec sentMessages *prometheus.CounterVec
relayedMessages *prometheus.CounterVec relayedMessages *prometheus.CounterVec
skippedOVM1Withdrawals *prometheus.CounterVec
skippedOVM1RelayedMessages prometheus.Counter
initiatedBridgeTransfers *prometheus.CounterVec initiatedBridgeTransfers *prometheus.CounterVec
finalizedBridgeTransfers *prometheus.CounterVec finalizedBridgeTransfers *prometheus.CounterVec
} }
...@@ -114,7 +122,7 @@ func NewMetrics(registry *prometheus.Registry) Metricer { ...@@ -114,7 +122,7 @@ func NewMetrics(registry *prometheus.Registry) Metricer {
txWithdrawals: factory.NewCounter(prometheus.CounterOpts{ txWithdrawals: factory.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace, Namespace: MetricsNamespace,
Name: "tx_withdrawals", Name: "tx_withdrawals",
Help: "number of processed transactions withdrawn from l2", Help: "number of initiated transaction withdrawals from l2",
}), }),
txWithdrawnETH: factory.NewCounter(prometheus.CounterOpts{ txWithdrawnETH: factory.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace, Namespace: MetricsNamespace,
...@@ -145,6 +153,18 @@ func NewMetrics(registry *prometheus.Registry) Metricer { ...@@ -145,6 +153,18 @@ func NewMetrics(registry *prometheus.Registry) Metricer {
}, []string{ }, []string{
"chain", "chain",
}), }),
skippedOVM1Withdrawals: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "skipped_ovm1_withdrawals",
Help: "number of skipped ovm 1.0 withdrawals on l1 (proven|finalized)",
}, []string{
"stage",
}),
skippedOVM1RelayedMessages: factory.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "skipped_ovm1_relayed_messages",
Help: "number of skipped ovm 1.0 relayed messages on l1",
}),
initiatedBridgeTransfers: factory.NewCounterVec(prometheus.CounterOpts{ initiatedBridgeTransfers: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace, Namespace: MetricsNamespace,
Name: "initiated_token_transfers", Name: "initiated_token_transfers",
...@@ -206,6 +226,18 @@ func (m *bridgeMetrics) RecordL1CrossDomainRelayedMessages(size int) { ...@@ -206,6 +226,18 @@ func (m *bridgeMetrics) RecordL1CrossDomainRelayedMessages(size int) {
m.relayedMessages.WithLabelValues("l1").Add(float64(size)) m.relayedMessages.WithLabelValues("l1").Add(float64(size))
} }
func (m *bridgeMetrics) RecordL1SkippedOVM1ProvenWithdrawals(size int) {
m.skippedOVM1Withdrawals.WithLabelValues("stage", "proven").Add(float64(size))
}
func (m *bridgeMetrics) RecordL1SkippedOVM1FinalizedWithdrawals(size int) {
m.skippedOVM1Withdrawals.WithLabelValues("stage", "finalized").Add(float64(size))
}
func (m *bridgeMetrics) RecordL1SkippedOVM1CrossDomainRelayedMessages(size int) {
m.skippedOVM1RelayedMessages.Add(float64(size))
}
func (m *bridgeMetrics) RecordL1InitiatedBridgeTransfers(tokenAddr common.Address, size int) { func (m *bridgeMetrics) RecordL1InitiatedBridgeTransfers(tokenAddr common.Address, size int) {
m.initiatedBridgeTransfers.WithLabelValues("l1", tokenAddr.String()).Add(float64(size)) m.initiatedBridgeTransfers.WithLabelValues("l1", tokenAddr.String()).Add(float64(size))
} }
......
This diff is collapsed.
This diff is collapsed.
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/indexer/bigint" "github.com/ethereum-optimism/optimism/indexer/bigint"
...@@ -13,31 +12,6 @@ import ( ...@@ -13,31 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
) )
var (
// Standard ABI types copied from golang ABI tests
addressType, _ = abi.NewType("address", "", nil)
bytesType, _ = abi.NewType("bytes", "", nil)
uint256Type, _ = abi.NewType("uint256", "", nil)
CrossDomainMessengerLegacyRelayMessageEncoding = abi.NewMethod(
"relayMessage",
"relayMessage",
abi.Function,
"external", // mutability
false, // isConst
true, // payable
abi.Arguments{ // inputs
{Name: "target", Type: addressType},
{Name: "sender", Type: addressType},
{Name: "data", Type: bytesType},
{Name: "nonce", Type: uint256Type},
// The actual transaction on the legacy L1CrossDomainMessenger has a trailing
// proof argument but is ignored for the `XDomainCallData` encoding
},
abi.Arguments{}, // outputs
)
)
type CrossDomainMessengerSentMessageEvent struct { type CrossDomainMessengerSentMessageEvent struct {
Event *database.ContractEvent Event *database.ContractEvent
BridgeMessage database.BridgeMessage BridgeMessage database.BridgeMessage
...@@ -117,7 +91,8 @@ func CrossDomainMessengerSentMessageEvents(chainSelector string, contractAddress ...@@ -117,7 +91,8 @@ func CrossDomainMessengerSentMessageEvents(chainSelector string, contractAddress
default: default:
// NOTE: We explicitly fail here since the presence of a new version means finalization // NOTE: We explicitly fail here since the presence of a new version means finalization
// logic needs to be updated to ensure L1 finalization can run from genesis and handle // logic needs to be updated to ensure L1 finalization can run from genesis and handle
// the changing version formats. This is meant to be a serving indicator of this. // the changing version formats. Any unrelayed OVM1 messages that have been harcoded with
// the v1 hash format also need to be updated. This failure is a serving indicator
return nil, fmt.Errorf("expected cross domain version 0 or version 1: %d", version) return nil, fmt.Errorf("expected cross domain version 0 or version 1: %d", version)
} }
......
...@@ -30,12 +30,6 @@ type OptimismPortalWithdrawalFinalizedEvent struct { ...@@ -30,12 +30,6 @@ type OptimismPortalWithdrawalFinalizedEvent struct {
Event *database.ContractEvent Event *database.ContractEvent
} }
type OptimismPortalProvenWithdrawal struct {
OutputRoot [32]byte
Timestamp *big.Int
L2OutputIndex *big.Int
}
func OptimismPortalTransactionDepositEvents(contractAddress common.Address, db *database.DB, fromHeight, toHeight *big.Int) ([]OptimismPortalTransactionDepositEvent, error) { func OptimismPortalTransactionDepositEvents(contractAddress common.Address, db *database.DB, fromHeight, toHeight *big.Int) ([]OptimismPortalTransactionDepositEvent, error) {
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi() optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil { if err != nil {
......
...@@ -25,6 +25,7 @@ const ( ...@@ -25,6 +25,7 @@ const (
L1FeeVault = "0x420000000000000000000000000000000000001a" L1FeeVault = "0x420000000000000000000000000000000000001a"
SchemaRegistry = "0x4200000000000000000000000000000000000020" SchemaRegistry = "0x4200000000000000000000000000000000000020"
EAS = "0x4200000000000000000000000000000000000021" EAS = "0x4200000000000000000000000000000000000021"
Create2Deployer = "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"
) )
var ( var (
...@@ -47,39 +48,48 @@ var ( ...@@ -47,39 +48,48 @@ var (
L1FeeVaultAddr = common.HexToAddress(L1FeeVault) L1FeeVaultAddr = common.HexToAddress(L1FeeVault)
SchemaRegistryAddr = common.HexToAddress(SchemaRegistry) SchemaRegistryAddr = common.HexToAddress(SchemaRegistry)
EASAddr = common.HexToAddress(EAS) EASAddr = common.HexToAddress(EAS)
Create2DeployerAddr = common.HexToAddress(Create2Deployer)
Predeploys = make(map[string]*common.Address) Predeploys = make(map[string]*Predeploy)
PredeploysByAddress = make(map[common.Address]*Predeploy)
) )
// IsProxied returns true for predeploys that will sit behind a proxy contract func init() {
func IsProxied(predeployAddr common.Address) bool { Predeploys["L2ToL1MessagePasser"] = &Predeploy{Address: L2ToL1MessagePasserAddr}
switch predeployAddr { Predeploys["DeployerWhitelist"] = &Predeploy{Address: DeployerWhitelistAddr}
case WETH9Addr: Predeploys["WETH9"] = &Predeploy{Address: WETH9Addr, ProxyDisabled: true}
case GovernanceTokenAddr: Predeploys["L2CrossDomainMessenger"] = &Predeploy{Address: L2CrossDomainMessengerAddr}
default: Predeploys["L2StandardBridge"] = &Predeploy{Address: L2StandardBridgeAddr}
return true Predeploys["SequencerFeeVault"] = &Predeploy{Address: SequencerFeeVaultAddr}
Predeploys["OptimismMintableERC20Factory"] = &Predeploy{Address: OptimismMintableERC20FactoryAddr}
Predeploys["L1BlockNumber"] = &Predeploy{Address: L1BlockNumberAddr}
Predeploys["GasPriceOracle"] = &Predeploy{Address: GasPriceOracleAddr}
Predeploys["L1Block"] = &Predeploy{Address: L1BlockAddr}
Predeploys["GovernanceToken"] = &Predeploy{
Address: GovernanceTokenAddr,
ProxyDisabled: true,
Enabled: func(config DeployConfig) bool {
return config.GovernanceEnabled()
},
}
Predeploys["LegacyMessagePasser"] = &Predeploy{Address: LegacyMessagePasserAddr}
Predeploys["L2ERC721Bridge"] = &Predeploy{Address: L2ERC721BridgeAddr}
Predeploys["OptimismMintableERC721Factory"] = &Predeploy{Address: OptimismMintableERC721FactoryAddr}
Predeploys["ProxyAdmin"] = &Predeploy{Address: ProxyAdminAddr}
Predeploys["BaseFeeVault"] = &Predeploy{Address: BaseFeeVaultAddr}
Predeploys["L1FeeVault"] = &Predeploy{Address: L1FeeVaultAddr}
Predeploys["SchemaRegistry"] = &Predeploy{Address: SchemaRegistryAddr}
Predeploys["EAS"] = &Predeploy{Address: EASAddr}
Predeploys["Create2Deployer"] = &Predeploy{
Address: Create2DeployerAddr,
ProxyDisabled: true,
Enabled: func(config DeployConfig) bool {
canyonTime := config.CanyonTime(0)
return canyonTime != nil && *canyonTime == 0
},
} }
return false
}
func init() { for _, predeploy := range Predeploys {
Predeploys["L2ToL1MessagePasser"] = &L2ToL1MessagePasserAddr PredeploysByAddress[predeploy.Address] = predeploy
Predeploys["DeployerWhitelist"] = &DeployerWhitelistAddr }
Predeploys["WETH9"] = &WETH9Addr
Predeploys["L2CrossDomainMessenger"] = &L2CrossDomainMessengerAddr
Predeploys["L2StandardBridge"] = &L2StandardBridgeAddr
Predeploys["SequencerFeeVault"] = &SequencerFeeVaultAddr
Predeploys["OptimismMintableERC20Factory"] = &OptimismMintableERC20FactoryAddr
Predeploys["L1BlockNumber"] = &L1BlockNumberAddr
Predeploys["GasPriceOracle"] = &GasPriceOracleAddr
Predeploys["L1Block"] = &L1BlockAddr
Predeploys["GovernanceToken"] = &GovernanceTokenAddr
Predeploys["LegacyMessagePasser"] = &LegacyMessagePasserAddr
Predeploys["L2ERC721Bridge"] = &L2ERC721BridgeAddr
Predeploys["OptimismMintableERC721Factory"] = &OptimismMintableERC721FactoryAddr
Predeploys["ProxyAdmin"] = &ProxyAdminAddr
Predeploys["BaseFeeVault"] = &BaseFeeVaultAddr
Predeploys["L1FeeVault"] = &L1FeeVaultAddr
Predeploys["SchemaRegistry"] = &SchemaRegistryAddr
Predeploys["EAS"] = &EASAddr
} }
package predeploys
import (
"github.com/ethereum/go-ethereum/common"
)
type DeployConfig interface {
GovernanceEnabled() bool
CanyonTime(genesisTime uint64) *uint64
}
type Predeploy struct {
Address common.Address
ProxyDisabled bool
Enabled func(config DeployConfig) bool
}
...@@ -99,8 +99,8 @@ func entrypoint(ctx *cli.Context) error { ...@@ -99,8 +99,8 @@ func entrypoint(ctx *cli.Context) error {
log.Info("All predeploy proxies are set correctly") log.Info("All predeploy proxies are set correctly")
// Check that all of the defined predeploys are set up correctly // Check that all of the defined predeploys are set up correctly
for name, addr := range predeploys.Predeploys { for name, pre := range predeploys.Predeploys {
log.Info("Checking predeploy", "name", name, "address", addr.Hex()) log.Info("Checking predeploy", "name", name, "address", pre.Address.Hex())
if err := checkPredeployConfig(clients.L2Client, name); err != nil { if err := checkPredeployConfig(clients.L2Client, name); err != nil {
return err return err
} }
...@@ -112,7 +112,7 @@ func entrypoint(ctx *cli.Context) error { ...@@ -112,7 +112,7 @@ func entrypoint(ctx *cli.Context) error {
func checkPredeploy(client *ethclient.Client, i uint64) error { func checkPredeploy(client *ethclient.Client, i uint64) error {
bigAddr := new(big.Int).Or(genesis.BigL2PredeployNamespace, new(big.Int).SetUint64(i)) bigAddr := new(big.Int).Or(genesis.BigL2PredeployNamespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr) addr := common.BigToAddress(bigAddr)
if !predeploys.IsProxied(addr) { if pre, ok := predeploys.PredeploysByAddress[addr]; ok && pre.ProxyDisabled {
return nil return nil
} }
admin, err := getEIP1967AdminAddress(client, addr) admin, err := getEIP1967AdminAddress(client, addr)
...@@ -131,10 +131,10 @@ func checkPredeployConfig(client *ethclient.Client, name string) error { ...@@ -131,10 +131,10 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
if predeploy == nil { if predeploy == nil {
return fmt.Errorf("unknown predeploy %s", name) return fmt.Errorf("unknown predeploy %s", name)
} }
p := *predeploy p := predeploy.Address
g := new(errgroup.Group) g := new(errgroup.Group)
if predeploys.IsProxied(p) { if !predeploy.ProxyDisabled {
// Check that an implementation is set. If the implementation has been upgraded, // Check that an implementation is set. If the implementation has been upgraded,
// it will be considered non-standard. Ensure that there is code set at the implementation. // it will be considered non-standard. Ensure that there is code set at the implementation.
g.Go(func() error { g.Go(func() error {
......
...@@ -32,6 +32,11 @@ type Constructor struct { ...@@ -32,6 +32,11 @@ type Constructor struct {
Args []interface{} Args []interface{}
} }
type SuperchainPredeploy struct {
Name string
CodeHash common.Hash
}
type Deployment struct { type Deployment struct {
Name string Name string
Bytecode hexutil.Bytes Bytecode hexutil.Bytes
......
...@@ -437,6 +437,10 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error { ...@@ -437,6 +437,10 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
return nil return nil
} }
func (d *DeployConfig) GovernanceEnabled() bool {
return d.EnableGovernance
}
func (d *DeployConfig) RegolithTime(genesisTime uint64) *uint64 { func (d *DeployConfig) RegolithTime(genesisTime uint64) *uint64 {
if d.L2GenesisRegolithTimeOffset == nil { if d.L2GenesisRegolithTimeOffset == nil {
return nil return nil
......
...@@ -18,13 +18,13 @@ var ( ...@@ -18,13 +18,13 @@ var (
codeNamespace = common.HexToAddress("0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000") codeNamespace = common.HexToAddress("0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000")
// l2PredeployNamespace represents the namespace of L2 predeploys // l2PredeployNamespace represents the namespace of L2 predeploys
l2PredeployNamespace = common.HexToAddress("0x4200000000000000000000000000000000000000") l2PredeployNamespace = common.HexToAddress("0x4200000000000000000000000000000000000000")
// bigL2PredeployNamespace represents the predeploy namespace as a big.Int // BigL2PredeployNamespace represents the predeploy namespace as a big.Int
BigL2PredeployNamespace = new(big.Int).SetBytes(l2PredeployNamespace.Bytes()) BigL2PredeployNamespace = new(big.Int).SetBytes(l2PredeployNamespace.Bytes())
// bigCodeNamespace represents the predeploy namespace as a big.Int // bigCodeNamespace represents the predeploy namespace as a big.Int
bigCodeNameSpace = new(big.Int).SetBytes(codeNamespace.Bytes()) bigCodeNamespace = new(big.Int).SetBytes(codeNamespace.Bytes())
// implementationSlot represents the EIP 1967 implementation storage slot // ImplementationSlot represents the EIP 1967 implementation storage slot
ImplementationSlot = common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc") ImplementationSlot = common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
// implementationSlot represents the EIP 1967 admin storage slot // AdminSlot represents the EIP 1967 admin storage slot
AdminSlot = common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103") AdminSlot = common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
) )
...@@ -69,7 +69,7 @@ func AddressToCodeNamespace(addr common.Address) (common.Address, error) { ...@@ -69,7 +69,7 @@ func AddressToCodeNamespace(addr common.Address) (common.Address, error) {
return common.Address{}, fmt.Errorf("cannot handle non predeploy: %s", addr) return common.Address{}, fmt.Errorf("cannot handle non predeploy: %s", addr)
} }
bigAddress := new(big.Int).SetBytes(addr[18:]) bigAddress := new(big.Int).SetBytes(addr[18:])
num := new(big.Int).Or(bigCodeNameSpace, bigAddress) num := new(big.Int).Or(bigCodeNamespace, bigAddress)
return common.BigToAddress(num), nil return common.BigToAddress(num), nil
} }
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
// BuildL2DeveloperGenesis will build the L2 genesis block. // BuildL2Genesis will build the L2 genesis block.
func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) { func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
genspec, err := NewL2Genesis(config, l1StartBlock) genspec, err := NewL2Genesis(config, l1StartBlock)
if err != nil { if err != nil {
...@@ -50,25 +50,23 @@ func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Gene ...@@ -50,25 +50,23 @@ func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Gene
return nil, err return nil, err
} }
for name, predeploy := range predeploys.Predeploys { for name, predeploy := range predeploys.Predeploys {
addr := *predeploy if predeploy.Enabled != nil && !predeploy.Enabled(config) {
if addr == predeploys.GovernanceTokenAddr && !config.EnableGovernance { log.Warn("Skipping disabled predeploy.", "name", name, "address", predeploy.Address)
// there is no governance token configured, so skip the governance token predeploy
log.Warn("Governance is not enabled, skipping governance token predeploy.")
continue continue
} }
codeAddr := addr codeAddr := predeploy.Address
if predeploys.IsProxied(addr) { if !predeploy.ProxyDisabled {
codeAddr, err = AddressToCodeNamespace(addr) codeAddr, err = AddressToCodeNamespace(predeploy.Address)
if err != nil { if err != nil {
return nil, fmt.Errorf("error converting to code namespace: %w", err) return nil, fmt.Errorf("error converting to code namespace: %w", err)
} }
db.CreateAccount(codeAddr) db.CreateAccount(codeAddr)
db.SetState(addr, ImplementationSlot, eth.AddressAsLeftPaddedHash(codeAddr)) db.SetState(predeploy.Address, ImplementationSlot, eth.AddressAsLeftPaddedHash(codeAddr))
log.Info("Set proxy", "name", name, "address", addr, "implementation", codeAddr) log.Info("Set proxy", "name", name, "address", predeploy.Address, "implementation", codeAddr)
} else { } else if db.Exist(predeploy.Address) {
db.DeleteState(addr, AdminSlot) db.DeleteState(predeploy.Address, AdminSlot)
} }
if err := setupPredeploy(db, deployResults, storage, name, addr, codeAddr); err != nil { if err := setupPredeploy(db, deployResults, storage, name, predeploy.Address, codeAddr); err != nil {
return nil, err return nil, err
} }
code := db.GetCode(codeAddr) code := db.GetCode(codeAddr)
......
...@@ -18,6 +18,7 @@ import ( ...@@ -18,6 +18,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
...@@ -48,14 +49,14 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi ...@@ -48,14 +49,14 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi
require.NoError(t, err) require.NoError(t, err)
for name, predeploy := range predeploys.Predeploys { for name, predeploy := range predeploys.Predeploys {
addr := *predeploy addr := predeploy.Address
account, ok := gen.Alloc[addr] account, ok := gen.Alloc[addr]
require.Equal(t, true, ok, name) require.Equal(t, true, ok, name)
require.Greater(t, len(account.Code), 0) require.Greater(t, len(account.Code), 0)
adminSlot, ok := account.Storage[genesis.AdminSlot] adminSlot, ok := account.Storage[genesis.AdminSlot]
isProxy := predeploys.IsProxied(addr) || isProxy := !predeploy.ProxyDisabled ||
(!config.EnableGovernance && addr == predeploys.GovernanceTokenAddr) (!config.EnableGovernance && addr == predeploys.GovernanceTokenAddr)
if isProxy { if isProxy {
require.Equal(t, true, ok, name) require.Equal(t, true, ok, name)
...@@ -73,6 +74,10 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi ...@@ -73,6 +74,10 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi
require.Equal(t, common.Big1, gen.Alloc[addr].Balance) require.Equal(t, common.Big1, gen.Alloc[addr].Balance)
} }
create2Deployer := gen.Alloc[predeploys.Create2DeployerAddr]
codeHash := crypto.Keccak256Hash(create2Deployer.Code)
require.Equal(t, codeHash, immutables.Create2DeployerCodeHash)
if writeFile { if writeFile {
file, _ := json.MarshalIndent(gen, "", " ") file, _ := json.MarshalIndent(gen, "", " ")
_ = os.WriteFile("genesis.json", file, 0644) _ = os.WriteFile("genesis.json", file, 0644)
...@@ -86,7 +91,7 @@ func TestBuildL2MainnetGenesis(t *testing.T) { ...@@ -86,7 +91,7 @@ func TestBuildL2MainnetGenesis(t *testing.T) {
config.EnableGovernance = true config.EnableGovernance = true
config.FundDevAccounts = false config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config) gen := testBuildL2Genesis(t, config)
require.Equal(t, 2322, len(gen.Alloc)) require.Equal(t, 2323, len(gen.Alloc))
} }
func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) { func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
...@@ -95,5 +100,5 @@ func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) { ...@@ -95,5 +100,5 @@ func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
config.EnableGovernance = false config.EnableGovernance = false
config.FundDevAccounts = false config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config) gen := testBuildL2Genesis(t, config)
require.Equal(t, 2322, len(gen.Alloc)) require.Equal(t, 2323, len(gen.Alloc))
} }
...@@ -55,8 +55,7 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int ...@@ -55,8 +55,7 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int
} }
// SetPrecompileBalances will set a single wei at each precompile address. // SetPrecompileBalances will set a single wei at each precompile address.
// This is an optimization to make calling them cheaper. This should only // This is an optimization to make calling them cheaper.
// be used for devnets.
func SetPrecompileBalances(db vm.StateDB) { func SetPrecompileBalances(db vm.StateDB) {
for i := 0; i < PrecompileCount; i++ { for i := 0; i < PrecompileCount; i++ {
addr := common.BytesToAddress([]byte{byte(i)}) addr := common.BytesToAddress([]byte{byte(i)})
......
...@@ -41,5 +41,8 @@ ...@@ -41,5 +41,8 @@
"enableGovernance": true, "enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "0x0000000000000000000000000000000000000333" "governanceTokenOwner": "0x0000000000000000000000000000000000000333",
"l2GenesisRegolithTimeOffset": "0x0",
"l2GenesisCanyonTimeOffset": "0x0"
} }
...@@ -5,16 +5,17 @@ import ( ...@@ -5,16 +5,17 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
// ImmutableValues represents the values to be set in immutable code. // ImmutableValues represents the values to be set in immutable code.
...@@ -26,6 +27,8 @@ type ImmutableValues map[string]any ...@@ -26,6 +27,8 @@ type ImmutableValues map[string]any
// contracts. // contracts.
type ImmutableConfig map[string]ImmutableValues type ImmutableConfig map[string]ImmutableValues
var Create2DeployerCodeHash = common.HexToHash("0xb0550b5b431e30d38000efb7107aaa0ade03d48a7198a140edda9d27134468b2")
// Check does a sanity check that the specific values that // Check does a sanity check that the specific values that
// Optimism uses are set inside of the ImmutableConfig. // Optimism uses are set inside of the ImmutableConfig.
func (i ImmutableConfig) Check() error { func (i ImmutableConfig) Check() error {
...@@ -152,13 +155,19 @@ func BuildOptimism(immutable ImmutableConfig) (DeploymentResults, error) { ...@@ -152,13 +155,19 @@ func BuildOptimism(immutable ImmutableConfig) (DeploymentResults, error) {
Name: "SchemaRegistry", Name: "SchemaRegistry",
}, },
} }
return BuildL2(deployments) superchainPredeploys := []deployer.SuperchainPredeploy{
{
Name: "Create2Deployer",
CodeHash: Create2DeployerCodeHash,
},
}
return BuildL2(deployments, superchainPredeploys)
} }
// BuildL2 will deploy contracts to a simulated backend so that their immutables // BuildL2 will deploy contracts to a simulated backend so that their immutables
// can be properly set. The bytecode returned in the results is suitable to be // can be properly set. The bytecode returned in the results is suitable to be
// inserted into the state via state surgery. // inserted into the state via state surgery.
func BuildL2(constructors []deployer.Constructor) (DeploymentResults, error) { func BuildL2(constructors []deployer.Constructor, superchainPredeploys []deployer.SuperchainPredeploy) (DeploymentResults, error) {
log.Info("Creating L2 state") log.Info("Creating L2 state")
deployments, err := deployer.Deploy(deployer.NewL2Backend(), constructors, l2Deployer) deployments, err := deployer.Deploy(deployer.NewL2Backend(), constructors, l2Deployer)
if err != nil { if err != nil {
...@@ -168,6 +177,13 @@ func BuildL2(constructors []deployer.Constructor) (DeploymentResults, error) { ...@@ -168,6 +177,13 @@ func BuildL2(constructors []deployer.Constructor) (DeploymentResults, error) {
for _, dep := range deployments { for _, dep := range deployments {
results[dep.Name] = dep.Bytecode results[dep.Name] = dep.Bytecode
} }
for _, dep := range superchainPredeploys {
code, err := superchain.LoadContractBytecode(superchain.Hash(dep.CodeHash))
if err != nil {
return nil, err
}
results[dep.Name] = code
}
return results, nil return results, nil
} }
......
...@@ -65,6 +65,7 @@ func TestBuildOptimism(t *testing.T) { ...@@ -65,6 +65,7 @@ func TestBuildOptimism(t *testing.T) {
"LegacyERC20ETH": true, "LegacyERC20ETH": true,
"EAS": true, "EAS": true,
"SchemaRegistry": true, "SchemaRegistry": true,
"Create2Deployer": true,
} }
// Only the exact contracts that we care about are being // Only the exact contracts that we care about are being
......
...@@ -61,6 +61,10 @@ func (db *MemoryStateDB) CreateAccount(addr common.Address) { ...@@ -61,6 +61,10 @@ func (db *MemoryStateDB) CreateAccount(addr common.Address) {
db.rw.Lock() db.rw.Lock()
defer db.rw.Unlock() defer db.rw.Unlock()
db.createAccount(addr)
}
func (db *MemoryStateDB) createAccount(addr common.Address) {
if _, ok := db.genesis.Alloc[addr]; !ok { if _, ok := db.genesis.Alloc[addr]; !ok {
db.genesis.Alloc[addr] = core.GenesisAccount{ db.genesis.Alloc[addr] = core.GenesisAccount{
Code: []byte{}, Code: []byte{},
...@@ -69,7 +73,6 @@ func (db *MemoryStateDB) CreateAccount(addr common.Address) { ...@@ -69,7 +73,6 @@ func (db *MemoryStateDB) CreateAccount(addr common.Address) {
Nonce: 0, Nonce: 0,
} }
} }
} }
func (db *MemoryStateDB) SubBalance(addr common.Address, amount *big.Int) { func (db *MemoryStateDB) SubBalance(addr common.Address, amount *big.Int) {
...@@ -165,6 +168,8 @@ func (db *MemoryStateDB) SetCode(addr common.Address, code []byte) { ...@@ -165,6 +168,8 @@ func (db *MemoryStateDB) SetCode(addr common.Address, code []byte) {
db.rw.Lock() db.rw.Lock()
defer db.rw.Unlock() defer db.rw.Unlock()
db.createAccount(addr)
account, ok := db.genesis.Alloc[addr] account, ok := db.genesis.Alloc[addr]
if !ok { if !ok {
return return
......
...@@ -32,16 +32,16 @@ func NewDisputeGameFactoryContract(addr common.Address, caller *batching.MultiCa ...@@ -32,16 +32,16 @@ func NewDisputeGameFactoryContract(addr common.Address, caller *batching.MultiCa
}, nil }, nil
} }
func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockNum uint64) (uint64, error) { func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockHash common.Hash) (uint64, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByNumber(blockNum), f.contract.Call(methodGameCount)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockByHash(blockHash), f.contract.Call(methodGameCount))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to load game count: %w", err) return 0, fmt.Errorf("failed to load game count: %w", err)
} }
return result.GetBigInt(0).Uint64(), nil return result.GetBigInt(0).Uint64(), nil
} }
func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockNum uint64) (types.GameMetadata, error) { func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockHash common.Hash) (types.GameMetadata, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByNumber(blockNum), f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx))) result, err := f.multiCaller.SingleCall(ctx, batching.BlockByHash(blockHash), f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx)))
if err != nil { if err != nil {
return types.GameMetadata{}, fmt.Errorf("failed to load game %v: %w", idx, err) return types.GameMetadata{}, fmt.Errorf("failed to load game %v: %w", idx, err)
} }
......
...@@ -18,7 +18,7 @@ var ( ...@@ -18,7 +18,7 @@ var (
) )
func TestDisputeGameFactorySimpleGetters(t *testing.T) { func TestDisputeGameFactorySimpleGetters(t *testing.T) {
blockNum := uint64(23) blockHash := common.Hash{0xbb, 0xcd}
tests := []struct { tests := []struct {
method string method string
args []interface{} args []interface{}
...@@ -31,7 +31,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) { ...@@ -31,7 +31,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
result: big.NewInt(9876), result: big.NewInt(9876),
expected: uint64(9876), expected: uint64(9876),
call: func(game *DisputeGameFactoryContract) (any, error) { call: func(game *DisputeGameFactoryContract) (any, error) {
return game.GetGameCount(context.Background(), blockNum) return game.GetGameCount(context.Background(), blockHash)
}, },
}, },
} }
...@@ -39,7 +39,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) { ...@@ -39,7 +39,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
test := test test := test
t.Run(test.method, func(t *testing.T) { t.Run(test.method, func(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t) stubRpc, factory := setupDisputeGameFactoryTest(t)
stubRpc.SetResponse(factoryAddr, test.method, batching.BlockByNumber(blockNum), nil, []interface{}{test.result}) stubRpc.SetResponse(factoryAddr, test.method, batching.BlockByHash(blockHash), nil, []interface{}{test.result})
status, err := test.call(factory) status, err := test.call(factory)
require.NoError(t, err) require.NoError(t, err)
expected := test.expected expected := test.expected
...@@ -52,7 +52,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) { ...@@ -52,7 +52,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
} }
func TestLoadGame(t *testing.T) { func TestLoadGame(t *testing.T) {
blockNum := uint64(23) blockHash := common.Hash{0xbb, 0xce}
stubRpc, factory := setupDisputeGameFactoryTest(t) stubRpc, factory := setupDisputeGameFactoryTest(t)
game0 := types.GameMetadata{ game0 := types.GameMetadata{
GameType: 0, GameType: 0,
...@@ -71,18 +71,18 @@ func TestLoadGame(t *testing.T) { ...@@ -71,18 +71,18 @@ func TestLoadGame(t *testing.T) {
} }
expectedGames := []types.GameMetadata{game0, game1, game2} expectedGames := []types.GameMetadata{game0, game1, game2}
for idx, expected := range expectedGames { for idx, expected := range expectedGames {
expectGetGame(stubRpc, idx, blockNum, expected) expectGetGame(stubRpc, idx, blockHash, expected)
actual, err := factory.GetGame(context.Background(), uint64(idx), blockNum) actual, err := factory.GetGame(context.Background(), uint64(idx), blockHash)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
} }
} }
func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockNum uint64, game types.GameMetadata) { func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game types.GameMetadata) {
stubRpc.SetResponse( stubRpc.SetResponse(
factoryAddr, factoryAddr,
methodGameAtIndex, methodGameAtIndex,
batching.BlockByNumber(blockNum), batching.BlockByHash(blockHash),
[]interface{}{big.NewInt(int64(idx))}, []interface{}{big.NewInt(int64(idx))},
[]interface{}{ []interface{}{
game.GameType, game.GameType,
......
...@@ -26,6 +26,8 @@ var ( ...@@ -26,6 +26,8 @@ var (
alphabetGameType = uint8(255) alphabetGameType = uint8(255)
) )
type CloseFunc func()
type Registry interface { type Registry interface {
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator) RegisterGameType(gameType uint8, creator scheduler.PlayerCreator)
} }
...@@ -38,16 +40,27 @@ func RegisterGameTypes( ...@@ -38,16 +40,27 @@ func RegisterGameTypes(
cfg *config.Config, cfg *config.Config,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
client *ethclient.Client, client *ethclient.Client,
) { ) (CloseFunc, error) {
var closer CloseFunc
var l2Client *ethclient.Client
if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypeOutputCannon) {
l2, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
l2Client = l2
closer = l2Client.Close
}
if cfg.TraceTypeEnabled(config.TraceTypeOutputCannon) { if cfg.TraceTypeEnabled(config.TraceTypeOutputCannon) {
registerOutputCannon(registry, ctx, logger, m, cfg, txMgr, client) registerOutputCannon(registry, ctx, logger, m, cfg, txMgr, client, l2Client)
} }
if cfg.TraceTypeEnabled(config.TraceTypeCannon) { if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
registerCannon(registry, ctx, logger, m, cfg, txMgr, client) registerCannon(registry, ctx, logger, m, cfg, txMgr, client, l2Client)
} }
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) { if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
registerAlphabet(registry, ctx, logger, m, cfg, txMgr, client) registerAlphabet(registry, ctx, logger, m, cfg, txMgr, client)
} }
return closer, nil
} }
func registerOutputCannon( func registerOutputCannon(
...@@ -57,7 +70,8 @@ func registerOutputCannon( ...@@ -57,7 +70,8 @@ func registerOutputCannon(
m metrics.Metricer, m metrics.Metricer,
cfg *config.Config, cfg *config.Config,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
client *ethclient.Client) { client *ethclient.Client,
l2Client cannon.L2HeaderSource) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) { resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr) logger := logger.New("game", addr)
// TODO(client-pod#43): Updated contracts should expose this as the pre and post state blocks // TODO(client-pod#43): Updated contracts should expose this as the pre and post state blocks
...@@ -65,7 +79,7 @@ func registerOutputCannon( ...@@ -65,7 +79,7 @@ func registerOutputCannon(
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
accessor, err := outputs.NewOutputCannonTraceAccessor(ctx, logger, m, cfg, contract, dir, gameDepth, agreed.L2BlockNumber.Uint64(), disputed.L2BlockNumber.Uint64()) accessor, err := outputs.NewOutputCannonTraceAccessor(ctx, logger, m, cfg, l2Client, contract, dir, gameDepth, agreed.L2BlockNumber.Uint64(), disputed.L2BlockNumber.Uint64())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -88,13 +102,15 @@ func registerCannon( ...@@ -88,13 +102,15 @@ func registerCannon(
m metrics.Metricer, m metrics.Metricer,
cfg *config.Config, cfg *config.Config,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
client *ethclient.Client) { client *ethclient.Client,
l2Client cannon.L2HeaderSource) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) { resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr) logger := logger.New("game", addr)
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, faultTypes.NoLocalContext, dir, gameDepth) localInputs, err := cannon.FetchLocalInputs(ctx, contract, l2Client)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err) return nil, nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err)
} }
provider := cannon.NewTraceProvider(logger, m, cfg, faultTypes.NoLocalContext, localInputs, dir, gameDepth)
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error { validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract) return ValidateAbsolutePrestate(ctx, provider, contract)
} }
......
...@@ -18,8 +18,7 @@ type LocalGameInputs struct { ...@@ -18,8 +18,7 @@ type LocalGameInputs struct {
L2BlockNumber *big.Int L2BlockNumber *big.Int
} }
type L2DataSource interface { type L2HeaderSource interface {
ChainID(context.Context) (*big.Int, error)
HeaderByNumber(context.Context, *big.Int) (*ethtypes.Header, error) HeaderByNumber(context.Context, *big.Int) (*ethtypes.Header, error)
} }
...@@ -32,15 +31,15 @@ type GameInputsSource interface { ...@@ -32,15 +31,15 @@ type GameInputsSource interface {
GetProposals(ctx context.Context) (agreed contracts.Proposal, disputed contracts.Proposal, err error) GetProposals(ctx context.Context) (agreed contracts.Proposal, disputed contracts.Proposal, err error)
} }
func fetchLocalInputs(ctx context.Context, caller GameInputsSource, l2Client L2DataSource) (LocalGameInputs, error) { func FetchLocalInputs(ctx context.Context, caller GameInputsSource, l2Client L2HeaderSource) (LocalGameInputs, error) {
agreedOutput, claimedOutput, err := caller.GetProposals(ctx) agreedOutput, claimedOutput, err := caller.GetProposals(ctx)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err) return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err)
} }
return fetchLocalInputsFromProposals(ctx, caller, l2Client, agreedOutput, claimedOutput) return FetchLocalInputsFromProposals(ctx, caller, l2Client, agreedOutput, claimedOutput)
} }
func fetchLocalInputsFromProposals(ctx context.Context, caller L1HeadSource, l2Client L2DataSource, agreedOutput contracts.Proposal, claimedOutput contracts.Proposal) (LocalGameInputs, error) { func FetchLocalInputsFromProposals(ctx context.Context, caller L1HeadSource, l2Client L2HeaderSource, agreedOutput contracts.Proposal, claimedOutput contracts.Proposal) (LocalGameInputs, error) {
l1Head, err := caller.GetL1Head(ctx) l1Head, err := caller.GetL1Head(ctx)
if err != nil { if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch L1 head: %w", err) return LocalGameInputs{}, fmt.Errorf("fetch L1 head: %w", err)
......
...@@ -32,7 +32,7 @@ func TestFetchLocalInputs(t *testing.T) { ...@@ -32,7 +32,7 @@ func TestFetchLocalInputs(t *testing.T) {
}, },
} }
inputs, err := fetchLocalInputs(ctx, contract, l2Client) inputs, err := FetchLocalInputs(ctx, contract, l2Client)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, contract.l1Head, inputs.L1Head) require.Equal(t, contract.l1Head, inputs.L1Head)
...@@ -62,7 +62,7 @@ func TestFetchLocalInputsFromProposals(t *testing.T) { ...@@ -62,7 +62,7 @@ func TestFetchLocalInputsFromProposals(t *testing.T) {
}, },
} }
inputs, err := fetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, claimed) inputs, err := FetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, claimed)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, contract.l1Head, inputs.L1Head) require.Equal(t, contract.l1Head, inputs.L1Head)
...@@ -91,11 +91,11 @@ type mockL2DataSource struct { ...@@ -91,11 +91,11 @@ type mockL2DataSource struct {
header ethtypes.Header header ethtypes.Header
} }
func (s *mockL2DataSource) ChainID(ctx context.Context) (*big.Int, error) { func (s *mockL2DataSource) ChainID(_ context.Context) (*big.Int, error) {
return s.chainID, nil return s.chainID, nil
} }
func (s *mockL2DataSource) HeaderByNumber(ctx context.Context, num *big.Int) (*ethtypes.Header, error) { func (s *mockL2DataSource) HeaderByNumber(_ context.Context, num *big.Int) (*ethtypes.Header, error) {
if s.header.Number.Cmp(num) == 0 { if s.header.Number.Cmp(num) == 0 {
return &s.header, nil return &s.header, nil
} }
......
...@@ -9,12 +9,10 @@ import ( ...@@ -9,12 +9,10 @@ import (
"path/filepath" "path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
...@@ -56,44 +54,7 @@ type CannonTraceProvider struct { ...@@ -56,44 +54,7 @@ type CannonTraceProvider struct {
lastStep uint64 lastStep uint64
} }
func NewTraceProvider(ctx context.Context, logger log.Logger, m CannonMetricer, cfg *config.Config, gameContract *contracts.FaultDisputeGameContract, localContext common.Hash, dir string, gameDepth uint64) (*CannonTraceProvider, error) { func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, localContext common.Hash, localInputs LocalGameInputs, dir string, gameDepth uint64) *CannonTraceProvider {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
defer l2Client.Close() // Not needed after fetching the inputs
localInputs, err := fetchLocalInputs(ctx, gameContract, l2Client)
if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err)
}
return NewTraceProviderFromInputs(logger, m, cfg, localContext, localInputs, dir, gameDepth), nil
}
func NewTraceProviderFromProposals(
ctx context.Context,
logger log.Logger,
m CannonMetricer,
cfg *config.Config,
gameContract *contracts.FaultDisputeGameContract,
localContext common.Hash,
agreed contracts.Proposal,
claimed contracts.Proposal,
dir string,
gameDepth uint64,
) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
defer l2Client.Close() // Not needed after fetching the inputs
localInputs, err := fetchLocalInputsFromProposals(ctx, gameContract, l2Client, agreed, claimed)
if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err)
}
return NewTraceProviderFromInputs(logger, m, cfg, localContext, localInputs, dir, gameDepth), nil
}
func NewTraceProviderFromInputs(logger log.Logger, m CannonMetricer, cfg *config.Config, localContext common.Hash, localInputs LocalGameInputs, dir string, gameDepth uint64) *CannonTraceProvider {
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: logger, logger: logger,
dir: dir, dir: dir,
......
...@@ -21,6 +21,7 @@ func NewOutputCannonTraceAccessor( ...@@ -21,6 +21,7 @@ func NewOutputCannonTraceAccessor(
logger log.Logger, logger log.Logger,
m metrics.Metricer, m metrics.Metricer,
cfg *config.Config, cfg *config.Config,
l2Client cannon.L2HeaderSource,
contract *contracts.FaultDisputeGameContract, contract *contracts.FaultDisputeGameContract,
dir string, dir string,
gameDepth uint64, gameDepth uint64,
...@@ -38,10 +39,11 @@ func NewOutputCannonTraceAccessor( ...@@ -38,10 +39,11 @@ func NewOutputCannonTraceAccessor(
cannonCreator := func(ctx context.Context, localContext common.Hash, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) { cannonCreator := func(ctx context.Context, localContext common.Hash, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) {
logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext) logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext)
subdir := filepath.Join(dir, localContext.Hex()) subdir := filepath.Join(dir, localContext.Hex())
provider, err := cannon.NewTraceProviderFromProposals(ctx, logger, m, cfg, contract, localContext, agreed, claimed, subdir, bottomDepth) localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, claimed)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create cannon trace provider: %w", err) return nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err)
} }
provider := cannon.NewTraceProvider(logger, m, cfg, localContext, localInputs, subdir, bottomDepth)
return provider, nil return provider, nil
} }
......
...@@ -2,21 +2,17 @@ package loader ...@@ -2,21 +2,17 @@ package loader
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
) "github.com/ethereum/go-ethereum/common"
var (
ErrMissingBlockNumber = errors.New("game loader missing block number")
) )
// MinimalDisputeGameFactoryCaller is a minimal interface around [bindings.DisputeGameFactoryCaller]. // MinimalDisputeGameFactoryCaller is a minimal interface around [bindings.DisputeGameFactoryCaller].
// This needs to be updated if the [bindings.DisputeGameFactoryCaller] interface changes. // This needs to be updated if the [bindings.DisputeGameFactoryCaller] interface changes.
type MinimalDisputeGameFactoryCaller interface { type MinimalDisputeGameFactoryCaller interface {
GetGameCount(ctx context.Context, blockNum uint64) (uint64, error) GetGameCount(ctx context.Context, blockHash common.Hash) (uint64, error)
GetGame(ctx context.Context, idx uint64, blockNum uint64) (types.GameMetadata, error) GetGame(ctx context.Context, idx uint64, blockHash common.Hash) (types.GameMetadata, error)
} }
type GameLoader struct { type GameLoader struct {
...@@ -31,15 +27,15 @@ func NewGameLoader(caller MinimalDisputeGameFactoryCaller) *GameLoader { ...@@ -31,15 +27,15 @@ func NewGameLoader(caller MinimalDisputeGameFactoryCaller) *GameLoader {
} }
// FetchAllGamesAtBlock fetches all dispute games from the factory at a given block number. // FetchAllGamesAtBlock fetches all dispute games from the factory at a given block number.
func (l *GameLoader) FetchAllGamesAtBlock(ctx context.Context, earliestTimestamp uint64, blockNumber uint64) ([]types.GameMetadata, error) { func (l *GameLoader) FetchAllGamesAtBlock(ctx context.Context, earliestTimestamp uint64, blockHash common.Hash) ([]types.GameMetadata, error) {
gameCount, err := l.caller.GetGameCount(ctx, blockNumber) gameCount, err := l.caller.GetGameCount(ctx, blockHash)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch game count: %w", err) return nil, fmt.Errorf("failed to fetch game count: %w", err)
} }
games := make([]types.GameMetadata, 0, gameCount) games := make([]types.GameMetadata, 0, gameCount)
for i := gameCount; i > 0; i-- { for i := gameCount; i > 0; i-- {
game, err := l.caller.GetGame(ctx, i-1, blockNumber) game, err := l.caller.GetGame(ctx, i-1, blockHash)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch game at index %d: %w", i-1, err) return nil, fmt.Errorf("failed to fetch game at index %d: %w", i-1, err)
} }
......
...@@ -24,39 +24,39 @@ func TestGameLoader_FetchAllGames(t *testing.T) { ...@@ -24,39 +24,39 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
name string name string
caller *mockMinimalDisputeGameFactoryCaller caller *mockMinimalDisputeGameFactoryCaller
earliest uint64 earliest uint64
blockNumber uint64 blockHash common.Hash
expectedErr error expectedErr error
expectedLen int expectedLen int
}{ }{
{ {
name: "success", name: "success",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false), caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
blockNumber: 1, blockHash: common.Hash{0x01},
expectedLen: 10, expectedLen: 10,
}, },
{ {
name: "expired game ignored", name: "expired game ignored",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false), caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
earliest: 500, earliest: 500,
blockNumber: 1, blockHash: common.Hash{0x01},
expectedLen: 5, expectedLen: 5,
}, },
{ {
name: "game count error", name: "game count error",
caller: newMockMinimalDisputeGameFactoryCaller(10, true, false), caller: newMockMinimalDisputeGameFactoryCaller(10, true, false),
blockNumber: 1, blockHash: common.Hash{0x01},
expectedErr: gameCountErr, expectedErr: gameCountErr,
}, },
{ {
name: "game index error", name: "game index error",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, true), caller: newMockMinimalDisputeGameFactoryCaller(10, false, true),
blockNumber: 1, blockHash: common.Hash{0x01},
expectedErr: gameIndexErr, expectedErr: gameIndexErr,
}, },
{ {
name: "no games", name: "no games",
caller: newMockMinimalDisputeGameFactoryCaller(0, false, false), caller: newMockMinimalDisputeGameFactoryCaller(0, false, false),
blockNumber: 1, blockHash: common.Hash{0x01},
}, },
} }
...@@ -67,7 +67,7 @@ func TestGameLoader_FetchAllGames(t *testing.T) { ...@@ -67,7 +67,7 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
t.Parallel() t.Parallel()
loader := NewGameLoader(test.caller) loader := NewGameLoader(test.caller)
games, err := loader.FetchAllGamesAtBlock(context.Background(), test.earliest, test.blockNumber) games, err := loader.FetchAllGamesAtBlock(context.Background(), test.earliest, test.blockHash)
require.ErrorIs(t, err, test.expectedErr) require.ErrorIs(t, err, test.expectedErr)
require.Len(t, games, test.expectedLen) require.Len(t, games, test.expectedLen)
expectedGames := test.caller.games expectedGames := test.caller.games
...@@ -138,7 +138,7 @@ func newMockMinimalDisputeGameFactoryCaller(count uint64, gameCountErr bool, ind ...@@ -138,7 +138,7 @@ func newMockMinimalDisputeGameFactoryCaller(count uint64, gameCountErr bool, ind
} }
} }
func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, blockNum uint64) (uint64, error) { func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, _ common.Hash) (uint64, error) {
if m.gameCountErr { if m.gameCountErr {
return 0, gameCountErr return 0, gameCountErr
} }
...@@ -146,7 +146,7 @@ func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, bl ...@@ -146,7 +146,7 @@ func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, bl
return m.gameCount, nil return m.gameCount, nil
} }
func (m *mockMinimalDisputeGameFactoryCaller) GetGame(_ context.Context, index uint64, blockNum uint64) (types.GameMetadata, error) { func (m *mockMinimalDisputeGameFactoryCaller) GetGame(_ context.Context, index uint64, _ common.Hash) (types.GameMetadata, error) {
if m.indexErrors[index] { if m.indexErrors[index] {
return struct { return struct {
GameType uint8 GameType uint8
......
...@@ -23,7 +23,7 @@ type blockNumberFetcher func(ctx context.Context) (uint64, error) ...@@ -23,7 +23,7 @@ type blockNumberFetcher func(ctx context.Context) (uint64, error)
// gameSource loads information about the games available to play // gameSource loads information about the games available to play
type gameSource interface { type gameSource interface {
FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockNumber uint64) ([]types.GameMetadata, error) FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockHash common.Hash) ([]types.GameMetadata, error)
} }
type gameScheduler interface { type gameScheduler interface {
...@@ -101,8 +101,8 @@ func (m *gameMonitor) minGameTimestamp() uint64 { ...@@ -101,8 +101,8 @@ func (m *gameMonitor) minGameTimestamp() uint64 {
return 0 return 0
} }
func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error { func (m *gameMonitor) progressGames(ctx context.Context, blockHash common.Hash) error {
games, err := m.source.FetchAllGamesAtBlock(ctx, m.minGameTimestamp(), blockNum) games, err := m.source.FetchAllGamesAtBlock(ctx, m.minGameTimestamp(), blockHash)
if err != nil { if err != nil {
return fmt.Errorf("failed to load games: %w", err) return fmt.Errorf("failed to load games: %w", err)
} }
...@@ -123,7 +123,7 @@ func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error ...@@ -123,7 +123,7 @@ func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error
} }
func (m *gameMonitor) onNewL1Head(ctx context.Context, sig eth.L1BlockRef) { func (m *gameMonitor) onNewL1Head(ctx context.Context, sig eth.L1BlockRef) {
if err := m.progressGames(ctx, sig.Number); err != nil { if err := m.progressGames(ctx, sig.Hash); err != nil {
m.logger.Error("Failed to progress games", "err", err) m.logger.Error("Failed to progress games", "err", err)
} }
} }
......
...@@ -145,7 +145,7 @@ func TestMonitorCreateAndProgressGameAgents(t *testing.T) { ...@@ -145,7 +145,7 @@ func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
addr2 := common.Address{0xbb} addr2 := common.Address{0xbb}
source.games = []types.GameMetadata{newFDG(addr1, 9999), newFDG(addr2, 9999)} source.games = []types.GameMetadata{newFDG(addr1, 9999), newFDG(addr2, 9999)}
require.NoError(t, monitor.progressGames(context.Background(), uint64(1))) require.NoError(t, monitor.progressGames(context.Background(), common.Hash{0x01}))
require.Len(t, sched.scheduled, 1) require.Len(t, sched.scheduled, 1)
require.Equal(t, []common.Address{addr1, addr2}, sched.scheduled[0]) require.Equal(t, []common.Address{addr1, addr2}, sched.scheduled[0])
...@@ -157,7 +157,7 @@ func TestMonitorOnlyScheduleSpecifiedGame(t *testing.T) { ...@@ -157,7 +157,7 @@ func TestMonitorOnlyScheduleSpecifiedGame(t *testing.T) {
monitor, source, sched, _ := setupMonitorTest(t, []common.Address{addr2}) monitor, source, sched, _ := setupMonitorTest(t, []common.Address{addr2})
source.games = []types.GameMetadata{newFDG(addr1, 9999), newFDG(addr2, 9999)} source.games = []types.GameMetadata{newFDG(addr1, 9999), newFDG(addr2, 9999)}
require.NoError(t, monitor.progressGames(context.Background(), uint64(1))) require.NoError(t, monitor.progressGames(context.Background(), common.Hash{0x01}))
require.Len(t, sched.scheduled, 1) require.Len(t, sched.scheduled, 1)
require.Equal(t, []common.Address{addr2}, sched.scheduled[0]) require.Equal(t, []common.Address{addr2}, sched.scheduled[0])
...@@ -232,7 +232,7 @@ type stubGameSource struct { ...@@ -232,7 +232,7 @@ type stubGameSource struct {
func (s *stubGameSource) FetchAllGamesAtBlock( func (s *stubGameSource) FetchAllGamesAtBlock(
ctx context.Context, ctx context.Context,
earliest uint64, earliest uint64,
blockNumber uint64, blockHash common.Hash,
) ([]types.GameMetadata, error) { ) ([]types.GameMetadata, error) {
return s.games, nil return s.games, nil
} }
......
...@@ -34,6 +34,8 @@ type Service struct { ...@@ -34,6 +34,8 @@ type Service struct {
monitor *gameMonitor monitor *gameMonitor
sched *scheduler.Scheduler sched *scheduler.Scheduler
faultGamesCloser fault.CloseFunc
txMgr *txmgr.SimpleTxManager txMgr *txmgr.SimpleTxManager
loader *loader.GameLoader loader *loader.GameLoader
...@@ -83,8 +85,10 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error ...@@ -83,8 +85,10 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if err := s.initGameLoader(cfg); err != nil { if err := s.initGameLoader(cfg); err != nil {
return err return err
} }
if err := s.initScheduler(ctx, cfg); err != nil {
return err
}
s.initScheduler(ctx, cfg)
s.initMonitor(cfg) s.initMonitor(cfg)
s.metrics.RecordInfo(version.SimpleWithMeta) s.metrics.RecordInfo(version.SimpleWithMeta)
...@@ -162,12 +166,17 @@ func (s *Service) initGameLoader(cfg *config.Config) error { ...@@ -162,12 +166,17 @@ func (s *Service) initGameLoader(cfg *config.Config) error {
return nil return nil
} }
func (s *Service) initScheduler(ctx context.Context, cfg *config.Config) { func (s *Service) initScheduler(ctx context.Context, cfg *config.Config) error {
gameTypeRegistry := registry.NewGameTypeRegistry() gameTypeRegistry := registry.NewGameTypeRegistry()
fault.RegisterGameTypes(gameTypeRegistry, ctx, s.logger, s.metrics, cfg, s.txMgr, s.l1Client) closer, err := fault.RegisterGameTypes(gameTypeRegistry, ctx, s.logger, s.metrics, cfg, s.txMgr, s.l1Client)
if err != nil {
return err
}
s.faultGamesCloser = closer
disk := newDiskManager(cfg.Datadir) disk := newDiskManager(cfg.Datadir)
s.sched = scheduler.NewScheduler(s.logger, s.metrics, disk, cfg.MaxConcurrency, gameTypeRegistry.CreatePlayer) s.sched = scheduler.NewScheduler(s.logger, s.metrics, disk, cfg.MaxConcurrency, gameTypeRegistry.CreatePlayer)
return nil
} }
func (s *Service) initMonitor(cfg *config.Config) { func (s *Service) initMonitor(cfg *config.Config) {
...@@ -200,6 +209,9 @@ func (s *Service) Stop(ctx context.Context) error { ...@@ -200,6 +209,9 @@ func (s *Service) Stop(ctx context.Context) error {
if s.monitor != nil { if s.monitor != nil {
s.monitor.StopMonitoring() s.monitor.StopMonitoring()
} }
if s.faultGamesCloser != nil {
s.faultGamesCloser()
}
if s.pprofSrv != nil { if s.pprofSrv != nil {
if err := s.pprofSrv.Stop(ctx); err != nil { if err := s.pprofSrv.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close pprof server: %w", err)) result = errors.Join(result, fmt.Errorf("failed to close pprof server: %w", err))
......
...@@ -47,8 +47,12 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol ...@@ -47,8 +47,12 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
maxDepth := g.MaxDepth(ctx) maxDepth := g.MaxDepth(ctx)
gameContract, err := contracts.NewFaultDisputeGameContract(g.addr, batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)) gameContract, err := contracts.NewFaultDisputeGameContract(g.addr, batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize))
g.require.NoError(err, "Create game contract bindings") g.require.NoError(err, "Create game contract bindings")
provider, err := cannon.NewTraceProvider(ctx, logger, metrics.NoopMetrics, cfg, gameContract, types.NoLocalContext, filepath.Join(cfg.Datadir, "honest"), uint64(maxDepth)) l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
g.require.NoError(err, "create cannon trace provider") g.require.NoErrorf(err, "dial l2 client %v", cfg.CannonL2)
defer l2Client.Close() // Not needed after fetching the inputs
localInputs, err := cannon.FetchLocalInputs(ctx, gameContract, l2Client)
g.require.NoError(err, "fetch cannon local inputs")
provider := cannon.NewTraceProvider(logger, metrics.NoopMetrics, cfg, types.NoLocalContext, localInputs, filepath.Join(cfg.Datadir, "honest"), uint64(maxDepth))
return &HonestHelper{ return &HonestHelper{
t: g.t, t: g.t,
......
...@@ -221,7 +221,7 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll ...@@ -221,7 +221,7 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll
maxDepth, err := gameImpl.MAXGAMEDEPTH(opts) maxDepth, err := gameImpl.MAXGAMEDEPTH(opts)
h.require.NoError(err, "fetch max game depth") h.require.NoError(err, "fetch max game depth")
provider := cannon.NewTraceProviderFromInputs( provider := cannon.NewTraceProvider(
testlog.Logger(h.t, log.LvlInfo).New("role", "CorrectTrace"), testlog.Logger(h.t, log.LvlInfo).New("role", "CorrectTrace"),
metrics.NoopMetrics, metrics.NoopMetrics,
cfg, cfg,
......
...@@ -126,7 +126,7 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, al ...@@ -126,7 +126,7 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, al
engine := derivationPipeline engine := derivationPipeline
meteredEngine := NewMeteredEngine(cfg, engine, metrics, log) meteredEngine := NewMeteredEngine(cfg, engine, metrics, log)
sequencer := NewSequencer(log, cfg, meteredEngine, attrBuilder, findL1Origin, metrics) sequencer := NewSequencer(log, cfg, meteredEngine, attrBuilder, findL1Origin, metrics)
driverCtx, driverCancel := context.WithCancel(context.Background())
return &Driver{ return &Driver{
l1State: l1State, l1State: l1State,
derivation: derivationPipeline, derivation: derivationPipeline,
...@@ -138,7 +138,8 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, al ...@@ -138,7 +138,8 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, al
sequencerNotifs: sequencerStateListener, sequencerNotifs: sequencerStateListener,
config: cfg, config: cfg,
driverConfig: driverCfg, driverConfig: driverCfg,
done: make(chan struct{}), driverCtx: driverCtx,
driverCancel: driverCancel,
log: log, log: log,
snapshotLog: snapshotLog, snapshotLog: snapshotLog,
l1: l1, l1: l1,
......
...@@ -86,9 +86,11 @@ type Driver struct { ...@@ -86,9 +86,11 @@ type Driver struct {
metrics Metrics metrics Metrics
log log.Logger log log.Logger
snapshotLog log.Logger snapshotLog log.Logger
done chan struct{}
wg gosync.WaitGroup wg gosync.WaitGroup
driverCtx context.Context
driverCancel context.CancelFunc
} }
// Start starts up the state loop. // Start starts up the state loop.
...@@ -118,7 +120,7 @@ func (s *Driver) Start() error { ...@@ -118,7 +120,7 @@ func (s *Driver) Start() error {
} }
func (s *Driver) Close() error { func (s *Driver) Close() error {
s.done <- struct{}{} s.driverCancel()
s.wg.Wait() s.wg.Wait()
return nil return nil
} }
...@@ -167,9 +169,9 @@ func (s *Driver) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPa ...@@ -167,9 +169,9 @@ func (s *Driver) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPa
func (s *Driver) eventLoop() { func (s *Driver) eventLoop() {
defer s.wg.Done() defer s.wg.Done()
s.log.Info("State loop started") s.log.Info("State loop started")
defer s.log.Info("State loop returned")
ctx, cancel := context.WithCancel(context.Background()) defer s.driverCancel()
defer cancel()
// stepReqCh is used to request that the driver attempts to step forward by one L1 block. // stepReqCh is used to request that the driver attempts to step forward by one L1 block.
stepReqCh := make(chan struct{}, 1) stepReqCh := make(chan struct{}, 1)
...@@ -230,6 +232,10 @@ func (s *Driver) eventLoop() { ...@@ -230,6 +232,10 @@ func (s *Driver) eventLoop() {
lastUnsafeL2 := s.derivation.UnsafeL2Head() lastUnsafeL2 := s.derivation.UnsafeL2Head()
for { for {
if s.driverCtx.Err() != nil { // don't try to schedule/handle more work when we are closing.
return
}
// If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action. // If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action.
// This may adjust at any time based on fork-choice changes or previous errors. // This may adjust at any time based on fork-choice changes or previous errors.
// And avoid sequencing if the derivation pipeline indicates the engine is not ready. // And avoid sequencing if the derivation pipeline indicates the engine is not ready.
...@@ -266,7 +272,7 @@ func (s *Driver) eventLoop() { ...@@ -266,7 +272,7 @@ func (s *Driver) eventLoop() {
select { select {
case <-sequencerCh: case <-sequencerCh:
payload, err := s.sequencer.RunNextSequencerAction(ctx) payload, err := s.sequencer.RunNextSequencerAction(s.driverCtx)
if err != nil { if err != nil {
s.log.Error("Sequencer critical error", "err", err) s.log.Error("Sequencer critical error", "err", err)
return return
...@@ -274,7 +280,7 @@ func (s *Driver) eventLoop() { ...@@ -274,7 +280,7 @@ func (s *Driver) eventLoop() {
if s.network != nil && payload != nil { if s.network != nil && payload != nil {
// Publishing of unsafe data via p2p is optional. // Publishing of unsafe data via p2p is optional.
// Errors are not severe enough to change/halt sequencing but should be logged and metered. // Errors are not severe enough to change/halt sequencing but should be logged and metered.
if err := s.network.PublishL2Payload(ctx, payload); err != nil { if err := s.network.PublishL2Payload(s.driverCtx, payload); err != nil {
s.log.Warn("failed to publish newly created block", "id", payload.ID(), "err", err) s.log.Warn("failed to publish newly created block", "id", payload.ID(), "err", err)
s.metrics.RecordPublishingError() s.metrics.RecordPublishingError()
} }
...@@ -282,7 +288,7 @@ func (s *Driver) eventLoop() { ...@@ -282,7 +288,7 @@ func (s *Driver) eventLoop() {
planSequencerAction() // schedule the next sequencer action to keep the sequencing looping planSequencerAction() // schedule the next sequencer action to keep the sequencing looping
case <-altSyncTicker.C: case <-altSyncTicker.C:
// Check if there is a gap in the current unsafe payload queue. // Check if there is a gap in the current unsafe payload queue.
ctx, cancel := context.WithTimeout(ctx, time.Second*2) ctx, cancel := context.WithTimeout(s.driverCtx, time.Second*2)
err := s.checkForGapInUnsafeQueue(ctx) err := s.checkForGapInUnsafeQueue(ctx)
cancel() cancel()
if err != nil { if err != nil {
...@@ -311,7 +317,7 @@ func (s *Driver) eventLoop() { ...@@ -311,7 +317,7 @@ func (s *Driver) eventLoop() {
case <-stepReqCh: case <-stepReqCh:
s.metrics.SetDerivationIdle(false) s.metrics.SetDerivationIdle(false)
s.log.Debug("Derivation process step", "onto_origin", s.derivation.Origin(), "attempts", stepAttempts) s.log.Debug("Derivation process step", "onto_origin", s.derivation.Origin(), "attempts", stepAttempts)
err := s.derivation.Step(context.Background()) err := s.derivation.Step(s.driverCtx)
stepAttempts += 1 // count as attempt by default. We reset to 0 if we are making healthy progress. stepAttempts += 1 // count as attempt by default. We reset to 0 if we are making healthy progress.
if err == io.EOF { if err == io.EOF {
s.log.Debug("Derivation process went idle", "progress", s.derivation.Origin(), "err", err) s.log.Debug("Derivation process went idle", "progress", s.derivation.Origin(), "err", err)
...@@ -383,12 +389,12 @@ func (s *Driver) eventLoop() { ...@@ -383,12 +389,12 @@ func (s *Driver) eventLoop() {
s.driverConfig.SequencerStopped = true s.driverConfig.SequencerStopped = true
// Cancel any inflight block building. If we don't cancel this, we can resume sequencing an old block // Cancel any inflight block building. If we don't cancel this, we can resume sequencing an old block
// even if we've received new unsafe heads in the interim, causing us to introduce a re-org. // even if we've received new unsafe heads in the interim, causing us to introduce a re-org.
s.sequencer.CancelBuildingBlock(ctx) s.sequencer.CancelBuildingBlock(s.driverCtx)
respCh <- hashAndError{hash: s.derivation.UnsafeL2Head().Hash} respCh <- hashAndError{hash: s.derivation.UnsafeL2Head().Hash}
} }
case respCh := <-s.sequencerActive: case respCh := <-s.sequencerActive:
respCh <- !s.driverConfig.SequencerStopped respCh <- !s.driverConfig.SequencerStopped
case <-s.done: case <-s.driverCtx.Done():
return return
} }
} }
......
# @eth-optimism/drippie-mon # @eth-optimism/drippie-mon
## 0.5.5
### Patch Changes
- [#8306](https://github.com/ethereum-optimism/optimism/pull/8306) [`dcb252917`](https://github.com/ethereum-optimism/optimism/commit/dcb25291768ec0f2386486619971d5cd66fbb409) Thanks [@protolambda](https://github.com/protolambda)! - Fixed bug with custom chains not being able to set a custom portal address
## 0.5.4 ## 0.5.4
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/chain-mon", "name": "@eth-optimism/chain-mon",
"version": "0.5.4", "version": "0.5.5",
"description": "[Optimism] Chain monitoring services", "description": "[Optimism] Chain monitoring services",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -63,6 +63,6 @@ ...@@ -63,6 +63,6 @@
"@nomiclabs/hardhat-waffle": "^2.0.6", "@nomiclabs/hardhat-waffle": "^2.0.6",
"hardhat": "^2.19.1", "hardhat": "^2.19.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsx": "^4.3.0" "tsx": "^4.5.0"
} }
} }
...@@ -147,11 +147,10 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -147,11 +147,10 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
contracts.OptimismPortal = portalAddress contracts.OptimismPortal = portalAddress
this.logger.info('fetching L2OutputOracle contract from OptimismPortal') this.logger.info('fetching L2OutputOracle contract from OptimismPortal')
const opts = { const portalContract = getOEContract('OptimismPortal', l2ChainId, {
portalAddress, address: portalAddress,
signerOrProvider: this.options.l1RpcProvider, signerOrProvider: this.options.l1RpcProvider,
} })
const portalContract = getOEContract('OptimismPortal', l2ChainId, opts)
contracts.L2OutputOracle = await portalContract.L2_ORACLE() contracts.L2OutputOracle = await portalContract.L2_ORACLE()
} }
......
...@@ -18,19 +18,21 @@ ...@@ -18,19 +18,21 @@
"build:go-ffi": "(cd scripts/go-ffi && go build)", "build:go-ffi": "(cd scripts/go-ffi && go build)",
"autogen:invariant-docs": "npx tsx scripts/invariant-doc-gen.ts", "autogen:invariant-docs": "npx tsx scripts/invariant-doc-gen.ts",
"test": "pnpm build:go-ffi && forge test", "test": "pnpm build:go-ffi && forge test",
"genesis": "./scripts/generate-l2-genesis.sh",
"coverage": "pnpm build:go-ffi && forge coverage", "coverage": "pnpm build:go-ffi && forge coverage",
"coverage:lcov": "pnpm build:go-ffi && forge coverage --report lcov", "coverage:lcov": "pnpm build:go-ffi && forge coverage --report lcov",
"deploy": "./scripts/deploy.sh", "deploy": "./scripts/deploy.sh",
"gas-snapshot:no-build": "forge snapshot --match-contract GasBenchMark", "gas-snapshot:no-build": "forge snapshot --match-contract GasBenchMark",
"gas-snapshot": "pnpm build:go-ffi && pnpm gas-snapshot:no-build", "gas-snapshot": "pnpm build:go-ffi && pnpm gas-snapshot:no-build",
"storage-snapshot": "./scripts/storage-snapshot.sh", "storage-snapshot": "./scripts/storage-snapshot.sh",
"abi-snapshot": "npx tsx scripts/generate-snapshots.ts",
"semver-lock": "forge script scripts/SemverLock.s.sol", "semver-lock": "forge script scripts/SemverLock.s.sol",
"validate-deploy-configs": "./scripts/check-deploy-configs.sh", "validate-deploy-configs": "./scripts/check-deploy-configs.sh",
"validate-spacers:no-build": "npx tsx scripts/validate-spacers.ts", "validate-spacers:no-build": "npx tsx scripts/validate-spacers.ts",
"validate-spacers": "pnpm build && pnpm validate-spacers:no-build", "validate-spacers": "pnpm build && pnpm validate-spacers:no-build",
"slither": "./scripts/slither.sh", "slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh", "slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
"clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./scripts/go-ffi/go-ffi ./.testdata", "clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./scripts/go-ffi/go-ffi ./.testdata ./deployments/hardhat/*",
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"pre-pr:no-build": "pnpm gas-snapshot:no-build && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && pnpm bindings:go", "pre-pr:no-build": "pnpm gas-snapshot:no-build && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && pnpm bindings:go",
"pre-pr": "pnpm clean && pnpm build:go-ffi && pnpm build && pnpm pre-pr:no-build", "pre-pr": "pnpm clean && pnpm build:go-ffi && pnpm build && pnpm pre-pr:no-build",
...@@ -45,9 +47,9 @@ ...@@ -45,9 +47,9 @@
"lint": "pnpm lint:fix && pnpm lint:check" "lint": "pnpm lint:fix && pnpm lint:check"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/eslint-plugin": "^6.13.0",
"@typescript-eslint/parser": "^6.11.0", "@typescript-eslint/parser": "^6.11.0",
"tsx": "^4.3.0", "tsx": "^4.5.0",
"typescript": "^5.3.2" "typescript": "^5.3.2"
} }
} }
...@@ -30,6 +30,17 @@ struct Artifact { ...@@ -30,6 +30,17 @@ struct Artifact {
string userdoc; string userdoc;
} }
/// @notice Contains information about a storage slot. Mirrors the layout of the storage
/// slot object in Forge artifacts so that we can deserialize JSON into this struct.
struct StorageSlot {
uint256 astId;
string _contract;
string label;
uint256 offset;
string slot;
string _type;
}
/// @title Deployer /// @title Deployer
/// @author tynes /// @author tynes
/// @notice A contract that can make deploying and interacting with deployments easy. /// @notice A contract that can make deploying and interacting with deployments easy.
...@@ -427,6 +438,36 @@ abstract contract Deployer is Script { ...@@ -427,6 +438,36 @@ abstract contract Deployer is Script {
return string(res); return string(res);
} }
/// @dev Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract.
function getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) {
string memory storageLayout = getStorageLayout(_contractName);
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.echo,
" '",
storageLayout,
"'",
" | ",
Executables.jq,
" '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'"
);
bytes memory rawSlot = vm.parseJson(string(vm.ffi(command)));
slot_ = abi.decode(rawSlot, (StorageSlot));
}
/// @dev Returns the value of the internal `_initialized` storage slot for a given contract.
function loadInitializedSlot(string memory _contractName, bool _isProxy) internal returns (uint8 initialized_) {
StorageSlot memory slot = getInitializedSlot(_contractName);
if (_isProxy) {
_contractName = string.concat(_contractName, "Proxy");
}
bytes32 slotVal = vm.load(mustGetAddress(_contractName), bytes32(vm.parseUint(slot.slot)));
initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF);
}
/// @notice Adds a deployment to the temp deployments file /// @notice Adds a deployment to the temp deployments file
function _writeTemp(string memory _name, address _deployed) internal { function _writeTemp(string memory _name, address _deployed) internal {
vm.writeJson({ json: stdJson.serialize("", _name, _deployed), path: tempDeploymentsPath }); vm.writeJson({ json: stdJson.serialize("", _name, _deployed), path: tempDeploymentsPath });
......
...@@ -16,19 +16,17 @@ TESTDATA_DIR="$CONTRACTS_DIR/.testdata" ...@@ -16,19 +16,17 @@ TESTDATA_DIR="$CONTRACTS_DIR/.testdata"
OUTFILE_L2="$TESTDATA_DIR/genesis.json" OUTFILE_L2="$TESTDATA_DIR/genesis.json"
OUTFILE_ROLLUP="$TESTDATA_DIR/rollup.json" OUTFILE_ROLLUP="$TESTDATA_DIR/rollup.json"
OUTFILE_ALLOC="$TESTDATA_DIR/alloc.json" mkdir -p "$TESTDATA_DIR"
if [ ! -f "$DEPLOY_ARTIFACT" ]; then if [ ! -f "$DEPLOY_ARTIFACT" ]; then
forge script $CONTRACTS_DIR/scripts/Deploy.s.sol:Deploy 2>&1 /dev/null forge script $CONTRACTS_DIR/scripts/Deploy.s.sol:Deploy > /dev/null 2>&1
fi fi
if [ ! -d "$TESTDATA_DIR" ]; then if [ ! -f "$OUTFILE_L2" ]; then
mkdir -p "$TESTDATA_DIR"
go run $OP_NODE genesis l2 \ go run $OP_NODE genesis l2 \
--deploy-config "$CONTRACTS_DIR/deploy-config/hardhat.json" \ --deploy-config "$CONTRACTS_DIR/deploy-config/hardhat.json" \
--l1-deployments "$DEPLOY_ARTIFACT" \ --l1-deployments "$DEPLOY_ARTIFACT" \
--l1-starting-block "$L1_STARTING_BLOCK_PATH" \ --l1-starting-block "$L1_STARTING_BLOCK_PATH" \
--outfile.l2 "$OUTFILE_L2" \ --outfile.l2 "$OUTFILE_L2" \
--outfile.rollup "$OUTFILE_ROLLUP" >/dev/null 2>&1 --outfile.rollup "$OUTFILE_ROLLUP" > /dev/null 2>&1
fi fi
import fs from 'fs'
import path from 'path'
const outdir = process.argv[2] || path.join(__dirname, '..', 'snapshots')
const forgeArtifactsDir = path.join(__dirname, '..', 'forge-artifacts')
const getAllContracts = (): Array<string> => {
const paths = []
const readFilesRecursively = (dir: string) => {
const files = fs.readdirSync(dir)
for (const file of files) {
const filePath = path.join(dir, file)
const fileStat = fs.statSync(filePath)
if (fileStat.isDirectory()) {
readFilesRecursively(filePath)
} else {
paths.push(filePath)
}
}
}
readFilesRecursively(path.join(__dirname, '..', 'src'))
// Assumes there is a single contract per file
return paths
.filter((x) => x.endsWith('.sol'))
.map((p: string) => {
const b = path.basename(p)
return `${b}:${b.replace('.sol', '')}`
})
.sort()
}
type AbiSpecStorageLayoutEntry = {
label: string
slot: number
offset: number
type: string
bytes: number
}
const sortKeys = (obj: any) => {
if (typeof obj !== 'object' || obj === null) {
return obj
}
return Object.keys(obj)
.sort()
.reduce(
(acc, key) => {
acc[key] = sortKeys(obj[key])
return acc
},
Array.isArray(obj) ? [] : {}
)
}
const main = async () => {
console.log(`writing abi spec to ${outdir}`)
const storageLayoutDir = path.join(outdir, 'storageLayout')
const abiDir = path.join(outdir, 'abi')
fs.mkdirSync(storageLayoutDir, { recursive: true })
fs.mkdirSync(abiDir, { recursive: true })
const contracts = getAllContracts()
for (const contract of contracts) {
const toks = contract.split(':')
const contractFile = contract.split(':')[0]
const contractName = toks[1]
let artifactFile = path.join(
forgeArtifactsDir,
contractFile,
`${contractName}.json`
)
// NOTE: Read the first version in the directory. We may want to assert that all version's ABIs are identical
if (!fs.existsSync(artifactFile)) {
const filename = fs.readdirSync(path.dirname(artifactFile))[0]
artifactFile = path.join(path.dirname(artifactFile), filename)
}
const data = fs.readFileSync(artifactFile)
const artifact = JSON.parse(data.toString())
// ignore abstract contracts
if (artifact.bytecode.object === '0x') {
console.log(`ignoring interface ${contractName}`)
continue
}
// HACK: This is a hack to ignore libraries. Not robust against changes to solc's internal ast repr
const isContract = artifact.ast.nodes.some((node: any) => {
return (
node.nodeType === 'ContractDefinition' &&
node.name === contractName &&
node.contractKind === 'contract'
)
})
if (!isContract) {
console.log(`ignoring library/interface ${contractName}`)
continue
}
const storageLayout: AbiSpecStorageLayoutEntry[] = []
for (const storageEntry of artifact.storageLayout.storage) {
// convert ast-based type to solidity type
const typ = artifact.storageLayout.types[storageEntry.type]
if (typ === undefined) {
throw new Error(
`undefined type for ${contractName}:${storageEntry.label}`
)
}
storageLayout.push({
label: typ.label,
bytes: typ.numberOfBytes,
offset: storageEntry.offset,
slot: storageEntry.slot,
type: storageEntry.type,
})
}
// Sort snapshots for easier manual inspection
fs.writeFileSync(
`${abiDir}/${contractName}.json`,
JSON.stringify(sortKeys(artifact.abi), null, 2)
)
fs.writeFileSync(
`${storageLayoutDir}/${contractName}.json`,
JSON.stringify(sortKeys(storageLayout), null, 2)
)
}
}
main()
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "address",
"name": "newAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "oldAddress",
"type": "address"
}
],
"name": "AddressSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
}
],
"name": "getAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "address",
"name": "_address",
"type": "address"
}
],
"name": "setAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "_admin",
"type": "address"
},
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_version",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ADMIN",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PROOF_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address payable",
"name": "recipient",
"type": "address"
},
{
"internalType": "bytes32",
"name": "nonce",
"type": "bytes32"
}
],
"internalType": "struct Faucet.DripParameters",
"name": "_params",
"type": "tuple"
},
{
"internalType": "bytes32",
"name": "_id",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
}
],
"name": "verify",
"outputs": [
{
"internalType": "bool",
"name": "valid_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ReceivedETH",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "withdrawer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "WithdrewERC20",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "withdrawer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "WithdrewERC721",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "withdrawer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "WithdrewETH",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
}
],
"name": "CALL",
"outputs": [
{
"internalType": "bool",
"name": "success_",
"type": "bool"
},
{
"internalType": "bytes",
"name": "data_",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "DELEGATECALL",
"outputs": [
{
"internalType": "bool",
"name": "success_",
"type": "bool"
},
{
"internalType": "bytes",
"name": "data_",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ERC20",
"name": "_asset",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "withdrawERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ERC20",
"name": "_asset",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
}
],
"name": "withdrawERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ERC721",
"name": "_asset",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_id",
"type": "uint256"
}
],
"name": "withdrawERC721",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "withdrawETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_to",
"type": "address"
}
],
"name": "withdrawETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "about",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "val",
"type": "bytes"
}
],
"name": "AttestationCreated",
"type": "event"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "about",
"type": "address"
},
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "val",
"type": "bytes"
}
],
"internalType": "struct AttestationStation.AttestationData[]",
"name": "_attestations",
"type": "tuple[]"
}
],
"name": "attest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_about",
"type": "address"
},
{
"internalType": "bytes32",
"name": "_key",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_val",
"type": "bytes"
}
],
"name": "attest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "attestations",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "_recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "_minWithdrawalAmount",
"type": "uint256"
},
{
"internalType": "enum FeeVault.WithdrawalNetwork",
"name": "_withdrawalNetwork",
"type": "uint8"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "from",
"type": "address"
}
],
"name": "Withdrawal",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "enum FeeVault.WithdrawalNetwork",
"name": "withdrawalNetwork",
"type": "uint8"
}
],
"name": "Withdrawal",
"type": "event"
},
{
"inputs": [],
"name": "MIN_WITHDRAWAL_AMOUNT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "RECIPIENT",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WITHDRAWAL_NETWORK",
"outputs": [
{
"internalType": "enum FeeVault.WithdrawalNetwork",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalProcessed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
[
{
"inputs": [],
"name": "BlockHashNotPresent",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "Hash",
"name": "blockHash",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "Timestamp",
"name": "childTimestamp",
"type": "uint64"
}
],
"name": "Checkpoint",
"type": "event"
},
{
"inputs": [],
"name": "checkpoint",
"outputs": [
{
"internalType": "uint256",
"name": "blockNumber_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_blockNumber",
"type": "uint256"
}
],
"name": "load",
"outputs": [
{
"components": [
{
"internalType": "Hash",
"name": "hash",
"type": "bytes32"
},
{
"internalType": "Timestamp",
"name": "childTimestamp",
"type": "uint64"
}
],
"internalType": "struct BlockOracle.BlockInfo",
"name": "blockInfo_",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "uint256",
"name": "threshold",
"type": "uint256"
}
],
"indexed": false,
"internalType": "struct CheckBalanceLow.Params",
"name": "params",
"type": "tuple"
}
],
"name": "_EventToExposeStructInABI__Params",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_params",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "treasury",
"type": "address"
},
{
"internalType": "uint256",
"name": "threshold",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"indexed": false,
"internalType": "struct CheckGelatoLow.Params",
"name": "params",
"type": "tuple"
}
],
"name": "_EventToExposeStructInABI__Params",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_params",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "pure",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "vetoer_",
"type": "address"
},
{
"internalType": "address",
"name": "initiator_",
"type": "address"
},
{
"internalType": "address",
"name": "target_",
"type": "address"
},
{
"internalType": "uint256",
"name": "operatingDelay_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AlreadyDelayed",
"type": "error"
},
{
"inputs": [],
"name": "ForwardingEarly",
"type": "error"
},
{
"inputs": [],
"name": "TargetUnitialized",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "expected",
"type": "address"
},
{
"internalType": "address",
"name": "actual",
"type": "address"
}
],
"name": "Unauthorized",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "delay",
"type": "uint256"
}
],
"name": "DelayActivated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "Forwarded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "Initiated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "Vetoed",
"type": "event"
},
{
"stateMutability": "nonpayable",
"type": "fallback"
},
{
"inputs": [],
"name": "delay",
"outputs": [
{
"internalType": "uint256",
"name": "delay_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "initiator",
"outputs": [
{
"internalType": "address",
"name": "initiator_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
}
],
"name": "queuedAt",
"outputs": [
{
"internalType": "uint256",
"name": "queuedAt_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "target",
"outputs": [
{
"internalType": "address",
"name": "target_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "vetoer",
"outputs": [
{
"internalType": "address",
"name": "vetoer_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldOwner",
"type": "address"
}
],
"name": "WhitelistDisabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "whitelisted",
"type": "bool"
}
],
"name": "WhitelistStatusChanged",
"type": "event"
},
{
"inputs": [],
"name": "enableArbitraryContractDeployment",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_deployer",
"type": "address"
}
],
"name": "isDeployerAllowed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_deployer",
"type": "address"
},
{
"internalType": "bool",
"name": "_isWhitelisted",
"type": "bool"
}
],
"name": "setWhitelistedDeployer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "whitelist",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "Hash",
"name": "uuid",
"type": "bytes32"
}
],
"name": "GameAlreadyExists",
"type": "error"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "gameType",
"type": "uint8"
}
],
"name": "NoImplementation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "disputeProxy",
"type": "address"
},
{
"indexed": true,
"internalType": "GameType",
"name": "gameType",
"type": "uint8"
},
{
"indexed": true,
"internalType": "Claim",
"name": "rootClaim",
"type": "bytes32"
}
],
"name": "DisputeGameCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "impl",
"type": "address"
},
{
"indexed": true,
"internalType": "GameType",
"name": "gameType",
"type": "uint8"
}
],
"name": "ImplementationSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "version",
"type": "uint8"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "Claim",
"name": "_rootClaim",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_extraData",
"type": "bytes"
}
],
"name": "create",
"outputs": [
{
"internalType": "contract IDisputeGame",
"name": "proxy_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
}
],
"name": "gameAtIndex",
"outputs": [
{
"internalType": "GameType",
"name": "gameType_",
"type": "uint8"
},
{
"internalType": "Timestamp",
"name": "timestamp_",
"type": "uint64"
},
{
"internalType": "contract IDisputeGame",
"name": "proxy_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gameCount",
"outputs": [
{
"internalType": "uint256",
"name": "gameCount_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "",
"type": "uint8"
}
],
"name": "gameImpls",
"outputs": [
{
"internalType": "contract IDisputeGame",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "Claim",
"name": "_rootClaim",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_extraData",
"type": "bytes"
}
],
"name": "games",
"outputs": [
{
"internalType": "contract IDisputeGame",
"name": "proxy_",
"type": "address"
},
{
"internalType": "Timestamp",
"name": "timestamp_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "Claim",
"name": "_rootClaim",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_extraData",
"type": "bytes"
}
],
"name": "getGameUUID",
"outputs": [
{
"internalType": "Hash",
"name": "uuid_",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "contract IDisputeGame",
"name": "_impl",
"type": "address"
}
],
"name": "setImplementation",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
[
{
"stateMutability": "payable",
"type": "fallback"
},
{
"inputs": [],
"name": "getL1BlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"stateMutability": "payable",
"type": "fallback"
},
{
"inputs": [],
"name": "getImplementation",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_code",
"type": "bytes"
}
],
"name": "setCode",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_key",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_value",
"type": "bytes32"
}
],
"name": "setStorage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment