Commit 2a01915b authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

challenger: Add subcommand to list the credits in a game and when they unlock. (#10757)

parent c4a50034
package main
import (
"context"
"fmt"
"math/big"
"time"
"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/game/fault/contracts/metrics"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/eth"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
"golang.org/x/exp/maps"
)
func ListCredits(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
gameAddr, err := opservice.ParseAddress(ctx.String(GameAddressFlag.Name))
if err != nil {
return err
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
contract, err := contracts.NewFaultDisputeGameContract(ctx.Context, metrics.NoopContractMetrics, gameAddr, caller)
if err != nil {
return err
}
return listCredits(ctx.Context, contract)
}
func listCredits(ctx context.Context, game contracts.FaultDisputeGameContract) error {
claims, err := game.GetAllClaims(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to load claims: %w", err)
}
metadata, err := game.GetGameMetadata(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to load metadata: %w", err)
}
recipients := make(map[common.Address]bool)
for _, claim := range claims {
if claim.CounteredBy != (common.Address{}) {
recipients[claim.CounteredBy] = true
}
recipients[claim.Claimant] = true
}
if metadata.L2BlockNumberChallenger != (common.Address{}) {
recipients[metadata.L2BlockNumberChallenger] = true
}
balance, withdrawalDelay, wethAddress, err := game.GetBalanceAndDelay(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to get DelayedWETH info: %w", err)
}
claimants := maps.Keys(recipients)
withdrawals, err := game.GetWithdrawals(ctx, rpcblock.Latest, claimants...)
if err != nil {
return fmt.Errorf("failed to get withdrawals: %w", err)
}
lineFormat := "%-42v %12v %-19v\n"
info := fmt.Sprintf(lineFormat, "Claimant", "ETH", "Unlock Time")
for i, withdrawal := range withdrawals {
var amount string
if withdrawal.Amount.Cmp(big.NewInt(0)) == 0 {
amount = "-"
} else {
amount = fmt.Sprintf("%12.8f", eth.WeiToEther(withdrawal.Amount))
}
var unlockTime string
if withdrawal.Timestamp.Cmp(big.NewInt(0)) == 0 {
unlockTime = "-"
} else {
unlockTime = time.Unix(withdrawal.Timestamp.Int64(), 0).Add(withdrawalDelay).Format(time.DateTime)
}
info += fmt.Sprintf(lineFormat, claimants[i], amount, unlockTime)
}
fmt.Printf("DelayedWETH Contract: %v • Total Balance (ETH): %12.8f • Delay: %v\n%v\n",
wethAddress, eth.WeiToEther(balance), withdrawalDelay, info)
return nil
}
func listCreditsFlags() []cli.Flag {
cliFlags := []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
}
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var ListCreditsCommand = &cli.Command{
Name: "list-credits",
Usage: "List the credits in a dispute game",
Description: "Lists the credits in a dispute game",
Action: ListCredits,
Flags: listCreditsFlags(),
}
......@@ -51,6 +51,7 @@ func run(ctx context.Context, args []string, action ConfiguredLifecycle) error {
app.Commands = []*cli.Command{
ListGamesCommand,
ListClaimsCommand,
ListCreditsCommand,
CreateGameCommand,
MoveCommand,
ResolveCommand,
......
......@@ -346,13 +346,13 @@ func (f *FaultDisputeGameContractLatest) addGlobalDataTx(ctx context.Context, da
return oracle.AddGlobalDataTx(data)
}
func (f *FaultDisputeGameContractLatest) GetWithdrawals(ctx context.Context, block rpcblock.Block, gameAddr common.Address, recipients ...common.Address) ([]*WithdrawalRequest, error) {
func (f *FaultDisputeGameContractLatest) GetWithdrawals(ctx context.Context, block rpcblock.Block, recipients ...common.Address) ([]*WithdrawalRequest, error) {
defer f.metrics.StartContractRequest("GetWithdrawals")()
delayedWETH, err := f.getDelayedWETH(ctx, block)
if err != nil {
return nil, err
}
return delayedWETH.GetWithdrawals(ctx, block, gameAddr, recipients...)
return delayedWETH.GetWithdrawals(ctx, block, f.contract.Addr(), recipients...)
}
func (f *FaultDisputeGameContractLatest) getDelayedWETH(ctx context.Context, block rpcblock.Block) (*DelayedWETHContract, error) {
......@@ -615,7 +615,7 @@ type FaultDisputeGameContract interface {
ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error)
GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error)
UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
GetWithdrawals(ctx context.Context, block rpcblock.Block, gameAddr common.Address, recipients ...common.Address) ([]*WithdrawalRequest, error)
GetWithdrawals(ctx context.Context, block rpcblock.Block, recipients ...common.Address) ([]*WithdrawalRequest, error)
GetOracle(ctx context.Context) (*PreimageOracleContract, error)
GetMaxClockDuration(ctx context.Context) (time.Duration, error)
GetMaxGameDepth(ctx context.Context) (types.Depth, error)
......
......@@ -24,7 +24,7 @@ type GameCallerMetrics interface {
}
type GameCaller interface {
GetWithdrawals(context.Context, rpcblock.Block, common.Address, ...common.Address) ([]*contracts.WithdrawalRequest, error)
GetWithdrawals(context.Context, rpcblock.Block, ...common.Address) ([]*contracts.WithdrawalRequest, error)
GetGameMetadata(context.Context, rpcblock.Block) (contracts.GameMetadata, error)
GetAllClaims(context.Context, rpcblock.Block) ([]faultTypes.Claim, error)
BondCaller
......
......@@ -236,7 +236,7 @@ type mockGameCaller struct {
resolved map[int]bool
}
func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ common.Address, _ ...common.Address) ([]*contracts.WithdrawalRequest, error) {
func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ ...common.Address) ([]*contracts.WithdrawalRequest, error) {
m.withdrawalsCalls++
if m.withdrawalsErr != nil {
return nil, m.withdrawalsErr
......
......@@ -24,7 +24,7 @@ func NewWithdrawalsEnricher() *WithdrawalsEnricher {
func (w *WithdrawalsEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error {
recipients := maps.Keys(game.Recipients)
withdrawals, err := caller.GetWithdrawals(ctx, block, game.Proxy, recipients...)
withdrawals, err := caller.GetWithdrawals(ctx, block, recipients...)
if err != nil {
return fmt.Errorf("failed to fetch withdrawals: %w", err)
}
......
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