Skip to content

Commit

Permalink
op-challenger: Read created game address from receipt (ethereum-optim…
Browse files Browse the repository at this point in the history
…ism#10277)

Extracts the game creation logic into reusable code.
  • Loading branch information
ajsutton authored Apr 24, 2024
1 parent b4df5c6 commit bd80e58
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 17 deletions.
21 changes: 5 additions & 16 deletions op-challenger/cmd/create_game.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package main

import (
"context"
"fmt"

"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/flags"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/tools"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
Expand Down Expand Up @@ -43,23 +43,12 @@ func CreateGame(ctx *cli.Context) error {
return fmt.Errorf("failed to create dispute game factory bindings: %w", err)
}

txCandidate, err := contract.CreateTx(ctx.Context, uint32(traceType), outputRoot, l2BlockNum)
creator := tools.NewGameCreator(contract, txMgr)
gameAddr, err := creator.CreateGame(ctx.Context, outputRoot, traceType, l2BlockNum)
if err != nil {
return fmt.Errorf("failed to create tx: %w", err)
return fmt.Errorf("failed to create game: %w", err)
}

rct, err := txMgr.Send(context.Background(), txCandidate)
if err != nil {
return fmt.Errorf("failed to send tx: %w", err)
}
fmt.Printf("Sent create transaction with status %v, tx_hash: %s\n", rct.Status, rct.TxHash.String())

fetchedGameAddr, err := contract.GetGameFromParameters(context.Background(), uint32(traceType), outputRoot, l2BlockNum)
if err != nil {
return fmt.Errorf("failed to call games: %w", err)
}
fmt.Printf("Fetched Game Address: %s\n", fetchedGameAddr.String())

fmt.Printf("Fetched Game Address: %s\n", gameAddr.String())
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion op-challenger/cmd/list_games.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr
fmt.Printf(lineFormat, "Idx", "Game", "Type", "Created (Local)", "L2 Block", "Output Root", "Claims", "Status")
for idx, game := range infos {
if game.err != nil {
return err
return game.err
}
created := time.Unix(int64(game.Timestamp), 0).Format(time.DateTime)
fmt.Printf(lineFormat,
Expand Down
32 changes: 32 additions & 0 deletions op-challenger/game/fault/contracts/gamefactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package contracts

import (
"context"
"errors"
"fmt"
"math/big"

Expand All @@ -11,7 +12,9 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
)

const (
Expand All @@ -21,12 +24,19 @@ const (
methodInitBonds = "initBonds"
methodCreateGame = "create"
methodGames = "games"

eventDisputeGameCreated = "DisputeGameCreated"
)

var (
ErrEventNotFound = errors.New("event not found")
)

type DisputeGameFactoryContract struct {
metrics metrics.ContractMetricer
multiCaller *batching.MultiCaller
contract *batching.BoundContract
abi *abi.ABI
}

func NewDisputeGameFactoryContract(m metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) *DisputeGameFactoryContract {
Expand All @@ -35,6 +45,7 @@ func NewDisputeGameFactoryContract(m metrics.ContractMetricer, addr common.Addre
metrics: m,
multiCaller: caller,
contract: batching.NewBoundContract(factoryAbi, addr),
abi: factoryAbi,
}
}

Expand Down Expand Up @@ -157,6 +168,27 @@ func (f *DisputeGameFactoryContract) CreateTx(ctx context.Context, traceType uin
return candidate, err
}

func (f *DisputeGameFactoryContract) DecodeDisputeGameCreatedLog(rcpt *ethTypes.Receipt) (common.Address, uint32, common.Hash, error) {
for _, log := range rcpt.Logs {
if log.Address != f.contract.Addr() {
// Not from this contract
continue
}
name, result, err := f.contract.DecodeEvent(log)
if err != nil {
// Not a valid event
continue
}
if name != eventDisputeGameCreated {
// Not the event we're looking for
continue
}

return result.GetAddress(0), result.GetUint32(1), result.GetHash(2), nil
}
return common.Address{}, 0, common.Hash{}, fmt.Errorf("%w: %v", ErrEventNotFound, eventDisputeGameCreated)
}

func (f *DisputeGameFactoryContract) decodeGame(result *batching.CallResult) types.GameMetadata {
gameType := result.GetUint32(0)
timestamp := result.GetUint64(1)
Expand Down
68 changes: 68 additions & 0 deletions op-challenger/game/fault/contracts/gamefactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -195,6 +196,73 @@ func TestGetGameImpl(t *testing.T) {
require.Equal(t, gameImplAddr, actual)
}

func TestDecodeDisputeGameCreatedLog(t *testing.T) {
_, factory := setupDisputeGameFactoryTest(t)
fdgAbi := snapshots.LoadDisputeGameFactoryABI()
eventAbi := fdgAbi.Events[eventDisputeGameCreated]
gameAddr := common.Address{0x11}
gameType := uint32(4)
rootClaim := common.Hash{0xaa, 0xbb, 0xcc}

createValidReceipt := func() *ethTypes.Receipt {
return &ethTypes.Receipt{
Status: ethTypes.ReceiptStatusSuccessful,
ContractAddress: fdgAddr,
Logs: []*ethTypes.Log{
{
Address: fdgAddr,
Topics: []common.Hash{
eventAbi.ID,
common.BytesToHash(gameAddr.Bytes()),
common.BytesToHash(big.NewInt(int64(gameType)).Bytes()),
rootClaim,
},
},
},
}
}

t.Run("IgnoreIncorrectContract", func(t *testing.T) {
rcpt := createValidReceipt()
rcpt.Logs[0].Address = common.Address{0xff}
_, _, _, err := factory.DecodeDisputeGameCreatedLog(rcpt)
require.ErrorIs(t, err, ErrEventNotFound)
})

t.Run("IgnoreInvalidEvent", func(t *testing.T) {
rcpt := createValidReceipt()
rcpt.Logs[0].Topics = rcpt.Logs[0].Topics[0:2]
_, _, _, err := factory.DecodeDisputeGameCreatedLog(rcpt)
require.ErrorIs(t, err, ErrEventNotFound)
})

t.Run("IgnoreWrongEvent", func(t *testing.T) {
rcpt := createValidReceipt()
rcpt.Logs[0].Topics = []common.Hash{
fdgAbi.Events["ImplementationSet"].ID,
common.BytesToHash(common.Address{0x11}.Bytes()), // Implementation addr
common.BytesToHash(big.NewInt(4).Bytes()), // Game type

}
// Check the log is a valid ImplementationSet
name, _, err := factory.contract.DecodeEvent(rcpt.Logs[0])
require.NoError(t, err)
require.Equal(t, "ImplementationSet", name)

_, _, _, err = factory.DecodeDisputeGameCreatedLog(rcpt)
require.ErrorIs(t, err, ErrEventNotFound)
})

t.Run("ValidEvent", func(t *testing.T) {
rcpt := createValidReceipt()
actualGameAddr, actualGameType, actualRootClaim, err := factory.DecodeDisputeGameCreatedLog(rcpt)
require.NoError(t, err)
require.Equal(t, gameAddr, actualGameAddr)
require.Equal(t, gameType, actualGameType)
require.Equal(t, rootClaim, actualRootClaim)
})
}

func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game types.GameMetadata) {
stubRpc.SetResponse(
factoryAddr,
Expand Down
44 changes: 44 additions & 0 deletions op-challenger/tools/create_game.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package tools

import (
"context"
"fmt"

"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

type GameCreator struct {
contract *contracts.DisputeGameFactoryContract
txMgr txmgr.TxManager
}

func NewGameCreator(contract *contracts.DisputeGameFactoryContract, txMgr txmgr.TxManager) *GameCreator {
return &GameCreator{
contract: contract,
txMgr: txMgr,
}
}

func (g *GameCreator) CreateGame(ctx context.Context, outputRoot common.Hash, traceType uint64, l2BlockNum uint64) (common.Address, error) {
txCandidate, err := g.contract.CreateTx(ctx, uint32(traceType), outputRoot, l2BlockNum)
if err != nil {
return common.Address{}, fmt.Errorf("failed to create tx: %w", err)
}

rct, err := g.txMgr.Send(ctx, txCandidate)
if err != nil {
return common.Address{}, fmt.Errorf("failed to send tx: %w", err)
}
if rct.Status != types.ReceiptStatusSuccessful {
return common.Address{}, fmt.Errorf("game creation transaction (%v) reverted", rct.TxHash.Hex())
}

gameAddr, _, _, err := g.contract.DecodeDisputeGameCreatedLog(rct)
if err != nil {
return common.Address{}, fmt.Errorf("failed to decode game created: %w", err)
}
return gameAddr, nil
}
40 changes: 40 additions & 0 deletions op-service/sources/batching/bound.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

var (
ErrUnknownMethod = errors.New("unknown method")
ErrInvalidCall = errors.New("invalid call")
ErrUnknownEvent = errors.New("unknown event")
ErrInvalidEvent = errors.New("invalid event")
)

type BoundContract struct {
Expand Down Expand Up @@ -48,3 +51,40 @@ func (b *BoundContract) DecodeCall(data []byte) (string, *CallResult, error) {
}
return method.Name, &CallResult{args}, nil
}

func (b *BoundContract) DecodeEvent(log *types.Log) (string, *CallResult, error) {
if len(log.Topics) == 0 {
return "", nil, ErrUnknownEvent
}
event, err := b.abi.EventByID(log.Topics[0])
if err != nil {
return "", nil, fmt.Errorf("%w: %v", ErrUnknownEvent, err.Error())
}

argsMap := make(map[string]interface{})
var indexed abi.Arguments
for _, arg := range event.Inputs {
if arg.Indexed {
indexed = append(indexed, arg)
}
}
if err := abi.ParseTopicsIntoMap(argsMap, indexed, log.Topics[1:]); err != nil {
return "", nil, fmt.Errorf("%w indexed topics: %v", ErrInvalidEvent, err.Error())
}

nonIndexed := event.Inputs.NonIndexed()
if len(nonIndexed) > 0 {
if err := nonIndexed.UnpackIntoMap(argsMap, log.Data); err != nil {
return "", nil, fmt.Errorf("%w non-indexed topics: %v", ErrInvalidEvent, err.Error())
}
}
args := make([]interface{}, 0, len(event.Inputs))
for _, input := range event.Inputs {
val, ok := argsMap[input.Name]
if !ok {
return "", nil, fmt.Errorf("%w missing argument: %v", ErrUnknownEvent, input.Name)
}
args = append(args, val)
}
return event.Name, &CallResult{args}, nil
}
Loading

0 comments on commit bd80e58

Please sign in to comment.