Commit 47d008b2 authored by refcell's avatar refcell Committed by GitHub

fix(op-challenger): migrate scripts to hidden subcommands (#9337)

parent d99d425a
# op-challenger
The `op-challenger` is a modular **op-stack** challenge agent
written in golang for dispute games including, but not limited to, attestation games, fault
games, and validity games. To learn more about dispute games, visit the
[fault proof specs](../specs/fault-proof.md).
The `op-challenger` is a modular **op-stack** challenge agent written in
golang for dispute games including, but not limited to,attestation games,
fault games, and validity games. To learn more about dispute games, visit
the [fault proof specs][proof-specs].
## Quickstart
First, clone this repo. Then run:
```shell
cd op-challenger
make alphabet
```
[proof-specs]: https://specs.optimism.io/experimental/fault-proof/index.html
This creates a local devnet, starts a dispute game using the simple alphabet trace type and runs two op-challenger
instances with differing views of the correct alphabet to play the game.
## Quickstart
Alternatively, you can build the `op-challenger` binary directly using the pre-configured
[Makefile](./Makefile) target by running `make build`, and then running `./bin/op-challenger --help`
to see a list of available options.
To build the `op-challenger`, run `make` (which executes the `make build`
[Makefile](./Makefile) target). To view a list of available commands and
options, run `./bin/op-challenger --help`.
## Usage
`op-challenger` is configurable via command line flags and environment variables. The help menu
shows the available config options and can be accessed by running `./op-challenger --help`.
`op-challenger` is configurable via command line flags and environment
variables. The help menu shows the available config options and can be
accessed by running `./op-challenger --help`.
### Running with Cannon on Local Devnet
To run `op-challenger` against the local devnet, first ensure the required components are built and the devnet is running.
From the top level of the repository run:
To run `op-challenger` against the local devnet, first clean and run
the devnet from the root of the repository.
```shell
make devnet-clean
make op-challenger
make devnet-up
```
Then start `op-challenger` with:
Then build the `op-challenger` with `make op-challenger`.
Run the `op-challenger` with:
```shell
DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json)
./op-challenger/bin/op-challenger \
--trace-type cannon \
--l1-eth-rpc http://localhost:8545 \
--rollup-rpc http://localhost:9546 \
--game-factory-address $DISPUTE_GAME_FACTORY \
--datadir temp/challenger-data \
--cannon-rollup-config .devnet/rollup.json \
......@@ -56,71 +50,61 @@ DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json)
--num-confirmations 1
```
The mnemonic and hd-path above is a prefunded address on the devnet. The challenger respond to any created games by
posting the correct trace as the counter-claim. The scripts below can then be used to create and interact with games.
## Scripts
The mnemonic and hd-path above is a prefunded address on the devnet.
The challenger will monitor dispute games and respond to any invalid
claims by posting the correct trace as the counter-claim. The commands
below can then be used to create and interact with games.
The [scripts](scripts) directory contains a collection of scripts to assist with manually creating and playing games.
This are not intended to be used in production, only to support manual testing and to aid with understanding how
dispute games work. They also serve as examples of how to use `cast` to manually interact with the dispute game
contracts.
## Subcommands
### Understanding Revert Reasons
The `op-challenger` has a few subcommands to interact with on-chain
fault dispute games. The subcommands support game creation, performing
game moves, and viewing fault dispute game data. They should not be
used in production and are intended to provide convenient manual testing.
When actions performed by these scripts fails, they typically print a message that includes the
abi encoded revert reason provided by the contract. e.g.
```
Error:
(code: 3, message: execution reverted, data: Some(String("0x67fe1950")))
```
The `cast 4byte` command can be used to decode these revert reasons. e.g.
### create-game
```shell
$ cast 4byte 0x67fe1950
GameNotInProgress()
./bin/op-challenger create-game \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_FACTORY_ADDRESS> \
--output-root <OUTPUT_ROOT> \
--l2-block-num <L2_BLOCK_NUM> \
...<SIGNER_ARGS>
```
### Dependencies
These scripts assume that the following tools are installed and available on the current `PATH`:
* `cast` (https://book.getfoundry.sh/cast/)
* `jq` (https://jqlang.github.io/jq/)
* `bash`
Starts a new fault dispute game that disputes the latest output proposal
in the L2 output oracle.
### [create_game.sh](scripts/create_game.sh)
```shell
./scripts/create_game.sh <RPC_URL> <GAME_FACTORY_ADDRESS> <OUTPUT_ROOT> <L2_BLOCK_NUM> <SIGNER_ARGS>...
```
Starts a new fault dispute game that disputes the latest output proposal in the L2 output oracle.
* `RPC_URL` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_FACTORY_ADDRESS` - the address of the dispute game factory contract on L1.
* `OUTPUT_ROOT` a hex encoded 32 byte hash that is used as the proposed output root.
* `L2_BLOCK_NUM` the L2 block number the proposed output root is from.
* `SIGNER_ARGS` the remaining args are past as arguments to `cast` when sending transactions.
These arguments must specify a way for `cast` to sign the transactions.
* `SIGNER_ARGS` the remaining args are past as arguments to `cast` when sending
transactions. These arguments must specify a way for `cast` to sign the transactions.
See `cast send --help` for supported options.
Creating a dispute game requires sending two transactions. The first transaction creates a
checkpoint in the `BlockOracle` that records the L1 block that will be used as the L1 head
when generating the cannon execution trace. The second transaction then creates the actual
dispute game, specifying the disputed L2 block number and previously checkpointed L1 head block.
Optionally, you may specify the game type (aka "trace type") using the `--trace-type`
flag, which is set to the cannon trace type by default.
### move
### [move.sh](scripts/move.sh)
The `move` subcommand can be run with either the `--attack` or `--defend` flag,
but not both.
```shell
./scripts/move.sh <RPC_URL> <GAME_ADDRESS> (attack|defend) <PARENT_INDEX> <CLAIM> <SIGNER_ARGS>...
./bin/op-challenger move \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_ADDRESS> \
--attack \
--parent-index <PARENT_INDEX> \
--claim <CLAIM> \
...<SIGNER_ARGS>
```
Performs a move to either attack or defend the latest claim in the specified game.
* `RPC_URL` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_ADDRESS` - the address of the dispute game to perform the move in.
* `(attack|defend)` - the type of move to make.
* `attack` indicates that the state hash in your local cannon trace differs to the state
......@@ -134,40 +118,47 @@ Performs a move to either attack or defend the latest claim in the specified gam
These arguments must specify a way for `cast` to sign the transactions.
See `cast send --help` for supported options.
### [resolve.sh](scripts/resolve.sh)
### resolve
```shell
./scripts/resolve.sh <RPC_URL> <GAME_ADDRESS> <SIGNER_ARGS>...
./bin/op-challenger resolve \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_ADDRESS> \
...<SIGNER_ARGS>
```
Resolves a dispute game. Note that this will fail if the dispute game has already been resolved
or if the clocks have not yet expired and further moves are possible.
Resolves a dispute game. Note that this will fail if the dispute game has already
been resolved or if the clocks have not yet expired and further moves are possible.
If the game is resolved successfully, the result is printed.
* `RPC_URL` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_ADDRESS` - the address of the dispute game to resolve.
* `SIGNER_ARGS` the remaining args are past as arguments to `cast` when sending transactions.
These arguments must specify a way for `cast` to sign the transactions.
See `cast send --help` for supported options.
### [list_games.sh](scripts/list_games.sh)
### list-games
```shell
./scripts/list_games.sh <RPC> <GAME_FACTORY_ADDRESS>
./bin/op-challenger list-games \
--l1-eth-rpc <L1_ETH_RPC> \
--game-factory-address <GAME_FACTORY_ADDRESS>
```
Prints the games created by the game factory along with their current status.
* `RPC_URL` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_FACTORY_ADDRESS` - the address of the dispute game factory contract on L1.
### [list_claims.sh](scripts/list_claims.sh)
### list-claims
```shell
./scripts/list_claims.sh <RPC> <GAME_ADDR>
./bin/op-challenger list-games \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_ADDRESS>
```
Prints the list of current claims in a dispute game.
* `RPC_URL` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_ADDRESS` - the address of the dispute game to list the move in.
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"
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"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
var (
TraceTypeFlag = &cli.StringFlag{
Name: "trace-type",
Usage: "Trace types to support.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "TRACE_TYPE"),
Value: config.TraceTypeCannon.String(),
}
OutputRootFlag = &cli.StringFlag{
Name: "output-root",
Usage: "The output root for the fault dispute game.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "OUTPUT_ROOT"),
}
L2BlockNumFlag = &cli.StringFlag{
Name: "l2-block-num",
Usage: "The l2 block number for the game.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "L2_BLOCK_NUM"),
}
)
func CreateGame(ctx *cli.Context) error {
outputRoot := common.HexToHash(ctx.String(OutputRootFlag.Name))
traceType := ctx.Uint64(TraceTypeFlag.Name)
l2BlockNum := ctx.Uint64(L2BlockNumFlag.Name)
contract, txMgr, err := NewContractWithTxMgr[*contracts.DisputeGameFactoryContract](ctx, flags.FactoryAddressFlag.Name, contracts.NewDisputeGameFactoryContract)
if err != nil {
return fmt.Errorf("failed to create dispute game factory bindings: %w", err)
}
txCandidate, err := contract.CreateTx(uint32(traceType), outputRoot, l2BlockNum)
if err != nil {
return fmt.Errorf("failed to create tx: %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())
return nil
}
var createGameFlags = []cli.Flag{
flags.L1EthRpcFlag,
flags.FactoryAddressFlag,
OutputRootFlag,
L2BlockNumFlag,
}
func init() {
createGameFlags = append(createGameFlags, txmgr.CLIFlagsWithDefaults(flags.EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
createGameFlags = append(createGameFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
}
var CreateGameCommand = &cli.Command{
Name: "create-game",
Usage: "Creates a dispute game via the factory",
Description: "Creates a dispute game via the factory",
Action: CreateGame,
Flags: createGameFlags,
Hidden: true,
}
......@@ -17,7 +17,7 @@ var (
GameAddressFlag = &cli.StringFlag{
Name: "game-address",
Usage: "Address of the fault game contract.",
EnvVars: opservice.PrefixEnvVar("OP_CHALLENGER", "GAME_FACTORY_ADDRESS"),
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "GAME_ADDRESS"),
}
)
......
package main
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/flags"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
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"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
var (
AttackFlag = &cli.BoolFlag{
Name: "attack",
Usage: "An attack move. If true, the defend flag must not be set.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "ATTACK"),
}
DefendFlag = &cli.BoolFlag{
Name: "defend",
Usage: "A defending move. If true, the attack flag must not be set.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "DEFEND"),
}
ParentIndexFlag = &cli.StringFlag{
Name: "parent-index",
Usage: "The index of the claim to move on.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "PARENT_INDEX"),
}
ClaimFlag = &cli.StringFlag{
Name: "claim",
Usage: "The claim hash.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "CLAIM"),
}
)
func Move(ctx *cli.Context) error {
attack := ctx.Bool(AttackFlag.Name)
defend := ctx.Bool(DefendFlag.Name)
parentIndex := ctx.Uint64(ParentIndexFlag.Name)
claim := common.HexToHash(ctx.String(ClaimFlag.Name))
if attack && defend {
return fmt.Errorf("both attack and defense flags cannot be set")
}
contract, txMgr, err := NewContractWithTxMgr[*contracts.FaultDisputeGameContract](ctx, GameAddressFlag.Name, contracts.NewFaultDisputeGameContract)
if err != nil {
return fmt.Errorf("failed to create dispute game bindings: %w", err)
}
var tx txmgr.TxCandidate
if attack {
tx, err = contract.AttackTx(parentIndex, claim)
if err != nil {
return fmt.Errorf("failed to create attack tx: %w", err)
}
} else if defend {
tx, err = contract.DefendTx(parentIndex, claim)
if err != nil {
return fmt.Errorf("failed to create defense tx: %w", err)
}
} else {
return fmt.Errorf("either attack or defense flag must be set")
}
rct, err := txMgr.Send(context.Background(), tx)
if err != nil {
return fmt.Errorf("failed to send tx: %w", err)
}
fmt.Printf("Sent tx with status: %v, hash: %s\n", rct.Status, rct.TxHash.String())
return nil
}
var moveFlags = []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
AttackFlag,
DefendFlag,
ParentIndexFlag,
ClaimFlag,
}
func init() {
moveFlags = append(moveFlags, txmgr.CLIFlagsWithDefaults(flags.EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
moveFlags = append(moveFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
}
var MoveCommand = &cli.Command{
Name: "move",
Usage: "Creates and sends a move transaction to the dispute game",
Description: "Creates and sends a move transaction to the dispute game",
Action: Move,
Flags: moveFlags,
Hidden: true,
}
package main
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/flags"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/urfave/cli/v2"
)
func Resolve(ctx *cli.Context) error {
contract, txMgr, err := NewContractWithTxMgr[*contracts.FaultDisputeGameContract](ctx, GameAddressFlag.Name, contracts.NewFaultDisputeGameContract)
if err != nil {
return fmt.Errorf("failed to create dispute game bindings: %w", err)
}
tx, err := contract.ResolveTx()
if err != nil {
return fmt.Errorf("failed to create resolve tx: %w", err)
}
rct, err := txMgr.Send(context.Background(), tx)
if err != nil {
return fmt.Errorf("failed to send tx: %w", err)
}
fmt.Printf("Sent resolve tx with status: %v, hash: %s\n", rct.Status, rct.TxHash.String())
return nil
}
var resolveFlags = []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
}
func init() {
resolveFlags = append(resolveFlags, txmgr.CLIFlagsWithDefaults(flags.EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
resolveFlags = append(resolveFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
}
var ResolveCommand = &cli.Command{
Name: "resolve",
Usage: "Resolves the specified dispute game if possible",
Description: "Resolves the specified dispute game if possible",
Action: Resolve,
Flags: resolveFlags,
Hidden: true,
}
package main
import (
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/flags"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
type ContractCreator[T any] func(common.Address, *batching.MultiCaller) (T, error)
// NewContractWithTxMgr creates a new contract and a transaction manager.
func NewContractWithTxMgr[T any](ctx *cli.Context, flagName string, creator ContractCreator[T]) (T, txmgr.TxManager, error) {
var contract T
caller, txMgr, err := newClientsFromCLI(ctx)
if err != nil {
return contract, nil, err
}
created, err := newContractFromCLI(ctx, flagName, caller, creator)
if err != nil {
return contract, nil, err
}
return created, txMgr, nil
}
// newContractFromCLI creates a new contract from the CLI context.
func newContractFromCLI[T any](ctx *cli.Context, flagName string, caller *batching.MultiCaller, creator ContractCreator[T]) (T, error) {
var contract T
gameAddr, err := opservice.ParseAddress(ctx.String(flagName))
if err != nil {
return contract, err
}
created, err := creator(gameAddr, caller)
if err != nil {
return contract, fmt.Errorf("failed to create dispute game bindings: %w", err)
}
return created, nil
}
// newClientsFromCLI creates a new caller and transaction manager from the CLI context.
func newClientsFromCLI(ctx *cli.Context) (*batching.MultiCaller, txmgr.TxManager, error) {
logger, err := setupLogging(ctx)
if err != nil {
return nil, nil, err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return nil, nil, fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return nil, nil, fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
txMgrConfig := txmgr.ReadCLIConfig(ctx)
txMgr, err := txmgr.NewSimpleTxManager("challenger", logger, &metrics.NoopTxMetrics{}, txMgrConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to create the transaction manager: %w", err)
}
return caller, txMgr, nil
}
......@@ -19,12 +19,10 @@ import (
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
const (
envVarPrefix = "OP_CHALLENGER"
)
const EnvVarPrefix = "OP_CHALLENGER"
func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(envVarPrefix, name)
return opservice.PrefixEnvVar(EnvVarPrefix, name)
}
var (
......@@ -167,10 +165,10 @@ var optionalFlags = []cli.Flag{
}
func init() {
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlagsWithDefaults(envVarPrefix, txmgr.DefaultChallengerFlagValues)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlagsWithDefaults(EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...)
}
......
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
......@@ -15,6 +16,8 @@ const (
methodGameCount = "gameCount"
methodGameAtIndex = "gameAtIndex"
methodGameImpls = "gameImpls"
methodCreateGame = "create"
methodGames = "games"
)
type DisputeGameFactoryContract struct {
......@@ -33,6 +36,14 @@ func NewDisputeGameFactoryContract(addr common.Address, caller *batching.MultiCa
}, nil
}
func (f *DisputeGameFactoryContract) GetGameFromParameters(ctx context.Context, traceType uint32, outputRoot common.Hash, l2BlockNum uint64) (common.Address, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodGames, traceType, outputRoot, common.BigToHash(big.NewInt(int64(l2BlockNum))).Bytes()))
if err != nil {
return common.Address{}, fmt.Errorf("failed to fetch game from parameters: %w", err)
}
return result.GetAddress(0), nil
}
func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockHash common.Hash) (uint64, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByHash(blockHash), f.contract.Call(methodGameCount))
if err != nil {
......@@ -80,6 +91,11 @@ func (f *DisputeGameFactoryContract) GetAllGames(ctx context.Context, blockHash
return games, nil
}
func (f *DisputeGameFactoryContract) CreateTx(traceType uint32, outputRoot common.Hash, l2BlockNum uint64) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodCreateGame, traceType, outputRoot, common.BigToHash(big.NewInt(int64(l2BlockNum))).Bytes())
return call.ToTxCandidate()
}
func (f *DisputeGameFactoryContract) decodeGame(result *batching.CallResult) types.GameMetadata {
gameType := result.GetUint32(0)
timestamp := result.GetUint64(1)
......
......@@ -105,13 +105,30 @@ func TestGetAllGames(t *testing.T) {
require.Equal(t, expectedGames, actualGames)
}
func TestGetGameFromParameters(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
traceType := uint32(123)
outputRoot := common.Hash{0x01}
l2BlockNum := common.BigToHash(big.NewInt(456)).Bytes()
stubRpc.SetResponse(
factoryAddr,
methodGames,
batching.BlockLatest,
[]interface{}{traceType, outputRoot, l2BlockNum},
[]interface{}{common.Address{0xaa}, uint64(1)},
)
addr, err := factory.GetGameFromParameters(context.Background(), traceType, outputRoot, uint64(456))
require.NoError(t, err)
require.Equal(t, common.Address{0xaa}, addr)
}
func TestGetGameImpl(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
gameType := uint32(3)
gameImplAddr := common.Address{0xaa}
stubRpc.SetResponse(
factoryAddr,
"gameImpls",
methodGameImpls,
batching.BlockLatest,
[]interface{}{gameType},
[]interface{}{gameImplAddr})
......@@ -133,6 +150,17 @@ func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.
})
}
func TestCreateTx(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
traceType := uint32(123)
outputRoot := common.Hash{0x01}
l2BlockNum := common.BigToHash(big.NewInt(456)).Bytes()
stubRpc.SetResponse(factoryAddr, methodCreateGame, batching.BlockLatest, []interface{}{traceType, outputRoot, l2BlockNum}, nil)
tx, err := factory.CreateTx(traceType, outputRoot, uint64(456))
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func setupDisputeGameFactoryTest(t *testing.T) (*batchingTest.AbiBasedRpc, *DisputeGameFactoryContract) {
fdgAbi, err := bindings.DisputeGameFactoryMetaData.GetAbi()
require.NoError(t, err)
......
#!/usr/bin/env bash
set -euo pipefail
SOURCE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
CHALLENGER_DIR="${SOURCE_DIR%/*}"
# ./create_game.sh <rpc-addr> <dispute-game-factory-addr> <output-root> <l2-block-num> <cast signing args>
RPC=${1:?Must specify RPC address}
FACTORY_ADDR=${2:?Must specify factory address}
ROOT_CLAIM=${3:?Must specify claimed output root}
L2_BLOCK_NUM=${4:?Must specify L2 block number of claimed output root}
SIGNER_ARGS=("${@:5}")
# Default to Cannon Fault game type
GAME_TYPE=${GAME_TYPE:-0}
# Fault dispute game extra data is calculated as follows.
# abi.encode(uint256(l2_block_number), uint256(l1 checkpoint))
EXTRA_DATA=$(cast abi-encode "f(uint256)" "${L2_BLOCK_NUM}" )
echo "Initializing the game"
FAULT_GAME_DATA=$(cast send --rpc-url "${RPC}" "${SIGNER_ARGS[@]}" "${FACTORY_ADDR}" "create(uint8,bytes32,bytes) returns(address)" "${GAME_TYPE}" "${ROOT_CLAIM}" "${EXTRA_DATA}" --json)
# Extract the address of the newly created game from the receipt logs.
FAULT_GAME_ADDRESS=$(echo "${FAULT_GAME_DATA}" | jq -r '.logs[0].topics[1]' | cast parse-bytes32-address)
echo "Fault game address: ${FAULT_GAME_ADDRESS}"
echo "${FAULT_GAME_ADDRESS}" > "$CHALLENGER_DIR"/.fault-game-address
#!/usr/bin/env bash
set -euo pipefail
RPC=${1:?Must specify RPC address}
GAME_ADDR=${2:?Must specify fault dispute game address}
COUNT=$(cast call --rpc-url "${RPC}" "${GAME_ADDR}" 'claimDataLen() returns(uint256)')
echo "Claim count: ${COUNT}"
((COUNT=COUNT-1))
for i in $(seq 0 "${COUNT}")
do
CLAIM=$(cast call --rpc-url "${RPC}" "${GAME_ADDR}" 'claimData(uint256) returns(uint32 parentIndex, address counteredBy, address claimant, uint128 bond, bytes32 claim, uint128 position, uint128 clock)' "${i}")
SAVEIFS=$IFS # Save current IFS (Internal Field Separator)
IFS=$'\n' # Change IFS to newline char
# shellcheck disable=SC2206
CLAIM=($CLAIM) # split the string into an array by the same name
IFS=$SAVEIFS # Restore original IFS
echo "${i} Parent: ${CLAIM[0]} Countered By: ${CLAIM[1]} Claimant: ${CLAIM[2]} Bond: ${CLAIM[3]} Claim: ${CLAIM[4]} Position: ${CLAIM[5]} Clock ${CLAIM[6]}"
done
#!/usr/bin/env bash
set -euo pipefail
RPC=${1:?Must specify RPC address}
FACTORY_ADDR=${2:?Must specify dispute game factory address}
COUNT=$(cast call --rpc-url "${RPC}" "${FACTORY_ADDR}" 'gameCount() returns(uint256)')
echo "Game count: ${COUNT}"
if [[ "${COUNT}" == "0" ]]
then
exit
fi
((COUNT=COUNT-1))
for i in $(seq 0 "${COUNT}")
do
GAME=$(cast call --rpc-url "${RPC}" "${FACTORY_ADDR}" 'gameAtIndex(uint256) returns(uint8, uint64, address)' "${i}")
SAVEIFS=$IFS # Save current IFS (Internal Field Separator)
IFS=$'\n' # Change IFS to newline char
# shellcheck disable=SC2206
GAME=($GAME) # split the string into an array by the same name
IFS=$SAVEIFS # Restore original IFS
GAME_ADDR="${GAME[2]}"
CLAIMS=$(cast call --rpc-url "${RPC}" "${GAME_ADDR}" "claimDataLen() returns(uint256)")
STATUS=$(cast call --rpc-url "${RPC}" "${GAME_ADDR}" "status() returns(uint8)" | cast to-dec)
if [[ "${STATUS}" == "0" ]]
then
STATUS="In Progress"
elif [[ "${STATUS}" == "1" ]]
then
STATUS="Challenger Wins"
elif [[ "${STATUS}" == "2" ]]
then
STATUS="Defender Wins"
fi
echo "${i} Game: ${GAME_ADDR} Type: ${GAME[0]} Created: ${GAME[1]} Claims: ${CLAIMS} Status: ${STATUS}"
done
#!/bin/bash
set -euo pipefail
RPC=${1:?Must specify RPC URL}
GAME_ADDR=${2:?Must specify game address}
ACTION=${3:?Must specify attack or defend}
PARENT_INDEX=${4:?Must specify parent index. Use latest to counter the latest claim added to the game.}
CLAIM=${5:?Must specify claim hash}
SIGNER_ARGS=("${@:6}")
if [[ "${ACTION}" != "attack" && "${ACTION}" != "defend" ]]
then
echo "Action must be either attack or defend"
exit 1
fi
if [[ "${PARENT_INDEX}" == "latest" ]]
then
# Fetch the index of the most recent claim made.
PARENT_INDEX=$(cast call --rpc-url "${RPC}" "${GAME_ADDR}" 'claimDataLen() returns(uint256)')
((PARENT_INDEX=PARENT_INDEX-1))
fi
# Perform the move.
cast send --rpc-url "${RPC}" "${SIGNER_ARGS[@]}" "${GAME_ADDR}" "$ACTION(uint256,bytes32)" "${PARENT_INDEX}" "${CLAIM}"
#!/bin/bash
set -euo pipefail
RPC=${1:?Must specify RPC URL}
GAME_ADDR=${2:?Must specify game address}
SIGNER_ARGS=("${@:3}")
# Perform the move.
# shellcheck disable=SC2086
RESULT_DATA=$(cast send --rpc-url "${RPC}" "${SIGNER_ARGS[@]}" "${GAME_ADDR}" "resolve()" --json)
RESULT=$(echo "${RESULT_DATA}" | jq -r '.logs[0].topics[1]' | cast to-dec)
if [[ "${RESULT}" == "0" ]]
then
RESULT="In Progress"
elif [[ "${RESULT}" == "1" ]]
then
RESULT="Challenger Wins"
elif [[ "${RESULT}" == "2" ]]
then
RESULT="Defender Wins"
fi
echo "Result: $RESULT"
#!/usr/bin/env bash
set -euo pipefail
RPC="${1:?Must specify RPC address}"
FAULT_GAME_ADDRESS="${2:?Must specify game address}"
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DIR="${DIR%/*/*}"
cd "$DIR"/packages/contracts-bedrock
DIR="${DIR%/*}"
cd "$DIR"
forge script scripts/FaultDisputeGameViz.s.sol \
--sig "remote(address)" "$FAULT_GAME_ADDRESS" \
--fork-url "$RPC"
mv dispute_game.svg "$DIR"
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