Commit 0280a281 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

Use the actual WETH delay when determining when bonds are withdrawable (#10674)

* Use the actual WETH delay when determining when bonds are withdrawable

* Change metric label and logs to reflect using withdrawable timing, not game resolution
parent 59e817b1
......@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
......@@ -14,6 +15,7 @@ import (
var (
methodWithdrawals = "withdrawals"
methodDelay = "delay"
)
type DelayedWETHContract struct {
......@@ -36,6 +38,28 @@ func NewDelayedWETHContract(metrics metrics.ContractMetricer, addr common.Addres
}
}
func (d *DelayedWETHContract) Addr() common.Address {
return d.contract.Addr()
}
// GetBalanceAndDelay returns the total amount of ETH controlled by this contract and the configured withdrawal delay.
func (d *DelayedWETHContract) GetBalanceAndDelay(ctx context.Context, block rpcblock.Block) (*big.Int, time.Duration, error) {
defer d.metrics.StartContractRequest("GetBalance")()
results, err := d.multiCaller.Call(ctx, block,
batching.NewBalanceCall(d.contract.Addr()),
d.contract.Call(methodDelay))
if err != nil {
return nil, 0, fmt.Errorf("failed to retrieve game balance: %w", err)
}
balance := results[0].GetBigInt(0)
delaySeconds := results[1].GetBigInt(0)
if !delaySeconds.IsInt64() {
return nil, 0, fmt.Errorf("withdrawal delay too big for int64 %v", delaySeconds)
}
delay := time.Duration(delaySeconds.Int64()) * time.Second
return balance, delay, nil
}
// GetWithdrawals returns all withdrawals made from the contract since the given block.
func (d *DelayedWETHContract) GetWithdrawals(ctx context.Context, block rpcblock.Block, gameAddr common.Address, recipients ...common.Address) ([]*WithdrawalRequest, error) {
defer d.metrics.StartContractRequest("GetWithdrawals")()
......
......@@ -4,6 +4,7 @@ import (
"context"
"math/big"
"testing"
"time"
contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
......@@ -24,8 +25,8 @@ func TestDelayedWeth_GetWithdrawals(t *testing.T) {
addrs := []common.Address{{0x01}, {0x02}}
expected := [][]*big.Int{
[]*big.Int{big.NewInt(123), big.NewInt(456)},
[]*big.Int{big.NewInt(123), big.NewInt(456)},
{big.NewInt(123), big.NewInt(456)},
{big.NewInt(123), big.NewInt(456)},
}
for i, addr := range addrs {
......@@ -41,6 +42,22 @@ func TestDelayedWeth_GetWithdrawals(t *testing.T) {
}
}
func TestDelayedWeth_GetBalanceAndDelay(t *testing.T) {
stubRpc, weth := setupDelayedWethTest(t)
block := rpcblock.ByNumber(482)
balance := big.NewInt(23984)
delaySeconds := int64(2983294824)
delay := time.Duration(delaySeconds) * time.Second
stubRpc.AddExpectedCall(batchingTest.NewGetBalanceCall(delayedWeth, block, balance))
stubRpc.SetResponse(delayedWeth, methodDelay, block, nil, []interface{}{big.NewInt(delaySeconds)})
actualBalance, actualDelay, err := weth.GetBalanceAndDelay(context.Background(), block)
require.NoError(t, err)
require.Equal(t, balance, actualBalance)
require.Equal(t, delay, actualDelay)
}
func setupDelayedWethTest(t *testing.T) (*batchingTest.AbiBasedRpc, *DelayedWETHContract) {
delayedWethAbi := snapshots.LoadDelayedWETHABI()
stubRpc := batchingTest.NewAbiBasedRpc(t, delayedWeth, delayedWethAbi)
......
......@@ -136,21 +136,20 @@ func mustParseAbi(json []byte) *abi.ABI {
return &loaded
}
// GetBalance returns the total amount of ETH controlled by this contract.
// GetBalanceAndDelay returns the total amount of ETH controlled by this contract.
// Note that the ETH is actually held by the DelayedWETH contract which may be shared by multiple games.
// Returns the balance and the address of the contract that actually holds the balance.
func (f *FaultDisputeGameContractLatest) GetBalance(ctx context.Context, block rpcblock.Block) (*big.Int, common.Address, error) {
defer f.metrics.StartContractRequest("GetBalance")()
result, err := f.multiCaller.SingleCall(ctx, block, f.contract.Call(methodWETH))
func (f *FaultDisputeGameContractLatest) GetBalanceAndDelay(ctx context.Context, block rpcblock.Block) (*big.Int, time.Duration, common.Address, error) {
defer f.metrics.StartContractRequest("GetBalanceAndDelay")()
weth, err := f.getDelayedWETH(ctx, block)
if err != nil {
return nil, common.Address{}, fmt.Errorf("failed to load weth address: %w", err)
return nil, 0, common.Address{}, fmt.Errorf("failed to get DelayedWETH contract: %w", err)
}
wethAddr := result.GetAddress(0)
result, err = f.multiCaller.SingleCall(ctx, block, batching.NewBalanceCall(wethAddr))
balance, delay, err := weth.GetBalanceAndDelay(ctx, block)
if err != nil {
return nil, common.Address{}, fmt.Errorf("failed to retrieve game balance: %w", err)
return nil, 0, common.Address{}, fmt.Errorf("failed to get WETH balance and delay: %w", err)
}
return result.GetBigInt(0), wethAddr, nil
return balance, delay, weth.Addr(), nil
}
// GetBlockRange returns the block numbers of the absolute pre-state block (typically genesis or the bedrock activation block)
......@@ -338,16 +337,16 @@ func (f *FaultDisputeGameContractLatest) addGlobalDataTx(ctx context.Context, da
func (f *FaultDisputeGameContractLatest) GetWithdrawals(ctx context.Context, block rpcblock.Block, gameAddr common.Address, recipients ...common.Address) ([]*WithdrawalRequest, error) {
defer f.metrics.StartContractRequest("GetWithdrawals")()
delayedWETH, err := f.getDelayedWETH(ctx)
delayedWETH, err := f.getDelayedWETH(ctx, block)
if err != nil {
return nil, err
}
return delayedWETH.GetWithdrawals(ctx, block, gameAddr, recipients...)
}
func (f *FaultDisputeGameContractLatest) getDelayedWETH(ctx context.Context) (*DelayedWETHContract, error) {
func (f *FaultDisputeGameContractLatest) getDelayedWETH(ctx context.Context, block rpcblock.Block) (*DelayedWETHContract, error) {
defer f.metrics.StartContractRequest("GetDelayedWETH")()
result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodWETH))
result, err := f.multiCaller.SingleCall(ctx, block, f.contract.Call(methodWETH))
if err != nil {
return nil, fmt.Errorf("failed to fetch WETH addr: %w", err)
}
......@@ -593,7 +592,7 @@ func (f *FaultDisputeGameContractLatest) decodeClaim(result *batching.CallResult
}
type FaultDisputeGameContract interface {
GetBalance(ctx context.Context, block rpcblock.Block) (*big.Int, common.Address, error)
GetBalanceAndDelay(ctx context.Context, block rpcblock.Block) (*big.Int, time.Duration, common.Address, error)
GetBlockRange(ctx context.Context) (prestateBlock uint64, poststateBlock uint64, retErr error)
GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error)
GetStartingRootHash(ctx context.Context) (common.Hash, error)
......
......@@ -327,14 +327,19 @@ func TestGetBalance(t *testing.T) {
t.Run(version.version, func(t *testing.T) {
wethAddr := common.Address{0x11, 0x55, 0x66}
balance := big.NewInt(9995877)
delaySeconds := big.NewInt(429829)
delay := time.Duration(delaySeconds.Int64()) * time.Second
block := rpcblock.ByNumber(424)
stubRpc, game := setupFaultDisputeGameTest(t, version)
stubRpc.SetResponse(fdgAddr, methodWETH, block, nil, []interface{}{wethAddr})
stubRpc.AddContract(wethAddr, snapshots.LoadDelayedWETHABI())
stubRpc.SetResponse(wethAddr, methodDelay, block, nil, []interface{}{delaySeconds})
stubRpc.AddExpectedCall(batchingTest.NewGetBalanceCall(wethAddr, block, balance))
actualBalance, actualAddr, err := game.GetBalance(context.Background(), block)
actualBalance, actualDelay, actualAddr, err := game.GetBalanceAndDelay(context.Background(), block)
require.NoError(t, err)
require.Equal(t, wethAddr, actualAddr)
require.Equal(t, delay, actualDelay)
require.Truef(t, balance.Cmp(actualBalance) == 0, "Expected balance %v but was %v", balance, actualBalance)
})
}
......
......@@ -41,14 +41,14 @@ type CreditExpectation uint8
const (
// Max Duration reached
CreditBelowMaxDuration CreditExpectation = iota
CreditEqualMaxDuration
CreditAboveMaxDuration
CreditBelowWithdrawable CreditExpectation = iota
CreditEqualWithdrawable
CreditAboveWithdrawable
// Max Duration not reached
CreditBelowNonMaxDuration
CreditEqualNonMaxDuration
CreditAboveNonMaxDuration
CreditBelowNonWithdrawable
CreditEqualNonWithdrawable
CreditAboveNonWithdrawable
)
type GameAgreementStatus uint8
......@@ -293,7 +293,7 @@ func NewMetrics() *Metrics {
Help: "Cumulative credits",
}, []string{
"credit",
"max_duration",
"withdrawable",
}),
claims: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
......@@ -434,18 +434,18 @@ func (m *Metrics) RecordGameResolutionStatus(status ResolutionStatus, count int)
func (m *Metrics) RecordCredit(expectation CreditExpectation, count int) {
asLabels := func(expectation CreditExpectation) []string {
switch expectation {
case CreditBelowMaxDuration:
return []string{"below", "max_duration"}
case CreditEqualMaxDuration:
return []string{"expected", "max_duration"}
case CreditAboveMaxDuration:
return []string{"above", "max_duration"}
case CreditBelowNonMaxDuration:
return []string{"below", "non_max_duration"}
case CreditEqualNonMaxDuration:
return []string{"expected", "non_max_duration"}
case CreditAboveNonMaxDuration:
return []string{"above", "non_max_duration"}
case CreditBelowWithdrawable:
return []string{"below", "withdrawable"}
case CreditEqualWithdrawable:
return []string{"expected", "withdrawable"}
case CreditAboveWithdrawable:
return []string{"above", "withdrawable"}
case CreditBelowNonWithdrawable:
return []string{"below", "non_withdrawable"}
case CreditEqualNonWithdrawable:
return []string{"expected", "non_withdrawable"}
case CreditAboveNonWithdrawable:
return []string{"above", "non_withdrawable"}
default:
panic(fmt.Errorf("unknown credit expectation: %v", expectation))
}
......
......@@ -51,7 +51,7 @@ func (b *Bonds) checkCredits(games []*types.EnrichedGameData) {
for _, game := range games {
// Check if the max duration has been reached for this game
duration := uint64(b.clock.Now().Unix()) - game.Timestamp
maxDurationReached := duration >= game.MaxClockDuration*2
maxDurationReached := duration >= game.MaxClockDuration+uint64(game.WETHDelay.Seconds())
// Iterate over claims, filter out resolved ones and sum up expected credits per recipient
expectedCredits := make(map[common.Address]*big.Int)
......@@ -95,32 +95,32 @@ func (b *Bonds) checkCredits(games []*types.EnrichedGameData) {
comparison := actual.Cmp(expected)
if maxDurationReached {
if comparison > 0 {
creditMetrics[metrics.CreditAboveMaxDuration] += 1
b.logger.Warn("Credit above expected amount", "recipient", recipient, "expected", expected, "actual", actual, "game", game.Proxy, "duration", "reached")
creditMetrics[metrics.CreditAboveWithdrawable] += 1
b.logger.Warn("Credit above expected amount", "recipient", recipient, "expected", expected, "actual", actual, "game", game.Proxy, "withdrawable", "withdrawable")
} else if comparison == 0 {
creditMetrics[metrics.CreditEqualMaxDuration] += 1
creditMetrics[metrics.CreditEqualWithdrawable] += 1
} else {
creditMetrics[metrics.CreditBelowMaxDuration] += 1
creditMetrics[metrics.CreditBelowWithdrawable] += 1
}
} else {
if comparison > 0 {
creditMetrics[metrics.CreditAboveNonMaxDuration] += 1
b.logger.Warn("Credit above expected amount", "recipient", recipient, "expected", expected, "actual", actual, "game", game.Proxy, "duration", "unreached")
creditMetrics[metrics.CreditAboveNonWithdrawable] += 1
b.logger.Warn("Credit above expected amount", "recipient", recipient, "expected", expected, "actual", actual, "game", game.Proxy, "withdrawable", "non_withdrawable")
} else if comparison == 0 {
creditMetrics[metrics.CreditEqualNonMaxDuration] += 1
creditMetrics[metrics.CreditEqualNonWithdrawable] += 1
} else {
creditMetrics[metrics.CreditBelowNonMaxDuration] += 1
b.logger.Warn("Credit withdrawn early", "recipient", recipient, "expected", expected, "actual", actual, "game", game.Proxy, "duration", "unreached")
creditMetrics[metrics.CreditBelowNonWithdrawable] += 1
b.logger.Error("Credit withdrawn early", "recipient", recipient, "expected", expected, "actual", actual, "game", game.Proxy, "withdrawable", "non_withdrawable")
}
}
}
}
b.metrics.RecordCredit(metrics.CreditBelowMaxDuration, creditMetrics[metrics.CreditBelowMaxDuration])
b.metrics.RecordCredit(metrics.CreditEqualMaxDuration, creditMetrics[metrics.CreditEqualMaxDuration])
b.metrics.RecordCredit(metrics.CreditAboveMaxDuration, creditMetrics[metrics.CreditAboveMaxDuration])
b.metrics.RecordCredit(metrics.CreditBelowWithdrawable, creditMetrics[metrics.CreditBelowWithdrawable])
b.metrics.RecordCredit(metrics.CreditEqualWithdrawable, creditMetrics[metrics.CreditEqualWithdrawable])
b.metrics.RecordCredit(metrics.CreditAboveWithdrawable, creditMetrics[metrics.CreditAboveWithdrawable])
b.metrics.RecordCredit(metrics.CreditBelowNonMaxDuration, creditMetrics[metrics.CreditBelowNonMaxDuration])
b.metrics.RecordCredit(metrics.CreditEqualNonMaxDuration, creditMetrics[metrics.CreditEqualNonMaxDuration])
b.metrics.RecordCredit(metrics.CreditAboveNonMaxDuration, creditMetrics[metrics.CreditAboveNonMaxDuration])
b.metrics.RecordCredit(metrics.CreditBelowNonWithdrawable, creditMetrics[metrics.CreditBelowNonWithdrawable])
b.metrics.RecordCredit(metrics.CreditEqualNonWithdrawable, creditMetrics[metrics.CreditEqualNonWithdrawable])
b.metrics.RecordCredit(metrics.CreditAboveNonWithdrawable, creditMetrics[metrics.CreditAboveNonWithdrawable])
}
......@@ -69,6 +69,7 @@ func TestCheckRecipientCredit(t *testing.T) {
// Game has not reached max duration
game1 := &monTypes.EnrichedGameData{
MaxClockDuration: 50000,
WETHDelay: 30 * time.Minute,
GameMetadata: gameTypes.GameMetadata{
Proxy: common.Address{0x11},
Timestamp: uint64(frozen.Unix()),
......@@ -141,6 +142,7 @@ func TestCheckRecipientCredit(t *testing.T) {
// Max duration has been reached
game2 := &monTypes.EnrichedGameData{
MaxClockDuration: 5,
WETHDelay: 5 * time.Second,
GameMetadata: gameTypes.GameMetadata{
Proxy: common.Address{0x22},
Timestamp: uint64(frozen.Unix()) - 11,
......@@ -215,6 +217,7 @@ func TestCheckRecipientCredit(t *testing.T) {
// Game has not reached max duration
game3 := &monTypes.EnrichedGameData{
MaxClockDuration: 50000,
WETHDelay: 10 * time.Hour,
GameMetadata: gameTypes.GameMetadata{
Proxy: common.Address{0x33},
Timestamp: uint64(frozen.Unix()) - 11,
......@@ -268,6 +271,7 @@ func TestCheckRecipientCredit(t *testing.T) {
// Game has not reached max duration
game4 := &monTypes.EnrichedGameData{
MaxClockDuration: 10,
WETHDelay: 10 * time.Second,
GameMetadata: gameTypes.GameMetadata{
Proxy: common.Address{44},
Timestamp: uint64(frozen.Unix()) - 22,
......@@ -325,46 +329,46 @@ func TestCheckRecipientCredit(t *testing.T) {
bonds.CheckBonds([]*monTypes.EnrichedGameData{game1, game2, game3, game4})
require.Len(t, m.credits, 6)
require.Contains(t, m.credits, metrics.CreditBelowMaxDuration)
require.Contains(t, m.credits, metrics.CreditEqualMaxDuration)
require.Contains(t, m.credits, metrics.CreditAboveMaxDuration)
require.Contains(t, m.credits, metrics.CreditBelowNonMaxDuration)
require.Contains(t, m.credits, metrics.CreditEqualNonMaxDuration)
require.Contains(t, m.credits, metrics.CreditAboveNonMaxDuration)
require.Contains(t, m.credits, metrics.CreditBelowWithdrawable)
require.Contains(t, m.credits, metrics.CreditEqualWithdrawable)
require.Contains(t, m.credits, metrics.CreditAboveWithdrawable)
require.Contains(t, m.credits, metrics.CreditBelowNonWithdrawable)
require.Contains(t, m.credits, metrics.CreditEqualNonWithdrawable)
require.Contains(t, m.credits, metrics.CreditAboveNonWithdrawable)
// Game 2 and 4 recipients added here as it has reached max duration
require.Equal(t, 2, m.credits[metrics.CreditBelowMaxDuration], "CreditBelowMaxDuration")
require.Equal(t, 3, m.credits[metrics.CreditEqualMaxDuration], "CreditEqualMaxDuration")
require.Equal(t, 2, m.credits[metrics.CreditAboveMaxDuration], "CreditAboveMaxDuration")
require.Equal(t, 2, m.credits[metrics.CreditBelowWithdrawable], "CreditBelowWithdrawable")
require.Equal(t, 3, m.credits[metrics.CreditEqualWithdrawable], "CreditEqualWithdrawable")
require.Equal(t, 2, m.credits[metrics.CreditAboveWithdrawable], "CreditAboveWithdrawable")
// Game 1 and 3 recipients added here as it hasn't reached max duration
require.Equal(t, 3, m.credits[metrics.CreditBelowNonMaxDuration], "CreditBelowNonMaxDuration")
require.Equal(t, 2, m.credits[metrics.CreditEqualNonMaxDuration], "CreditEqualNonMaxDuration")
require.Equal(t, 2, m.credits[metrics.CreditAboveNonMaxDuration], "CreditAboveNonMaxDuration")
require.Equal(t, 3, m.credits[metrics.CreditBelowNonWithdrawable], "CreditBelowNonWithdrawable")
require.Equal(t, 2, m.credits[metrics.CreditEqualNonWithdrawable], "CreditEqualNonWithdrawable")
require.Equal(t, 2, m.credits[metrics.CreditAboveNonWithdrawable], "CreditAboveNonWithdrawable")
// Logs from game1
// addr1 is correct so has no logs
// addr2 is below expected before max duration, so warn about early withdrawal
require.NotNil(t, logs.FindLog(
testlog.NewLevelFilter(log.LevelWarn),
testlog.NewLevelFilter(log.LevelError),
testlog.NewMessageFilter("Credit withdrawn early"),
testlog.NewAttributesFilter("game", game1.Proxy.Hex()),
testlog.NewAttributesFilter("recipient", addr2.Hex()),
testlog.NewAttributesFilter("duration", "unreached")))
testlog.NewAttributesFilter("withdrawable", "non_withdrawable")))
// addr3 is above expected
require.NotNil(t, logs.FindLog(
testlog.NewLevelFilter(log.LevelWarn),
testlog.NewMessageFilter("Credit above expected amount"),
testlog.NewAttributesFilter("game", game1.Proxy.Hex()),
testlog.NewAttributesFilter("recipient", addr3.Hex()),
testlog.NewAttributesFilter("duration", "unreached")))
testlog.NewAttributesFilter("withdrawable", "non_withdrawable")))
// addr4 is below expected before max duration, so warn about early withdrawal
require.NotNil(t, logs.FindLog(
testlog.NewLevelFilter(log.LevelWarn),
testlog.NewLevelFilter(log.LevelError),
testlog.NewMessageFilter("Credit withdrawn early"),
testlog.NewAttributesFilter("game", game1.Proxy.Hex()),
testlog.NewAttributesFilter("recipient", addr4.Hex()),
testlog.NewAttributesFilter("duration", "unreached")))
testlog.NewAttributesFilter("withdrawable", "non_withdrawable")))
// Logs from game 2
// addr1 is below expected - no warning as withdrawals may now be possible
......@@ -375,18 +379,18 @@ func TestCheckRecipientCredit(t *testing.T) {
testlog.NewMessageFilter("Credit above expected amount"),
testlog.NewAttributesFilter("game", game2.Proxy.Hex()),
testlog.NewAttributesFilter("recipient", addr3.Hex()),
testlog.NewAttributesFilter("duration", "reached")))
testlog.NewAttributesFilter("withdrawable", "withdrawable")))
// addr4 is correct
// Logs from game 3
// addr1 is correct so has no logs
// addr2 is below expected before max duration, so warn about early withdrawal
require.NotNil(t, logs.FindLog(
testlog.NewLevelFilter(log.LevelWarn),
testlog.NewLevelFilter(log.LevelError),
testlog.NewMessageFilter("Credit withdrawn early"),
testlog.NewAttributesFilter("game", game3.Proxy.Hex()),
testlog.NewAttributesFilter("recipient", addr2.Hex()),
testlog.NewAttributesFilter("duration", "unreached")))
testlog.NewAttributesFilter("withdrawable", "non_withdrawable")))
// addr3 is not involved so no logs
// addr4 is above expected before max duration, so warn
require.NotNil(t, logs.FindLog(
......@@ -394,7 +398,7 @@ func TestCheckRecipientCredit(t *testing.T) {
testlog.NewMessageFilter("Credit above expected amount"),
testlog.NewAttributesFilter("game", game3.Proxy.Hex()),
testlog.NewAttributesFilter("recipient", addr4.Hex()),
testlog.NewAttributesFilter("duration", "unreached")))
testlog.NewAttributesFilter("withdrawable", "non_withdrawable")))
// Logs from game 4
// addr1 is correct so has no logs
......@@ -406,7 +410,7 @@ func TestCheckRecipientCredit(t *testing.T) {
testlog.NewMessageFilter("Credit above expected amount"),
testlog.NewAttributesFilter("game", game4.Proxy.Hex()),
testlog.NewAttributesFilter("recipient", addr4.Hex()),
testlog.NewAttributesFilter("duration", "reached")))
testlog.NewAttributesFilter("withdrawable", "withdrawable")))
}
func setupBondMetricsTest(t *testing.T) (*Bonds, *stubBondMetrics, *testlog.CapturingHandler) {
......
......@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math/big"
"time"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
......@@ -13,7 +14,7 @@ import (
var _ Enricher = (*BalanceEnricher)(nil)
type BalanceCaller interface {
GetBalance(context.Context, rpcblock.Block) (*big.Int, common.Address, error)
GetBalanceAndDelay(context.Context, rpcblock.Block) (*big.Int, time.Duration, common.Address, error)
}
type BalanceEnricher struct{}
......@@ -23,11 +24,12 @@ func NewBalanceEnricher() *BalanceEnricher {
}
func (b *BalanceEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error {
balance, holdingAddr, err := caller.GetBalance(ctx, block)
balance, delay, holdingAddr, err := caller.GetBalanceAndDelay(ctx, block)
if err != nil {
return fmt.Errorf("failed to fetch balance: %w", err)
}
game.ETHCollateral = balance
game.WETHContract = holdingAddr
game.WETHDelay = delay
return nil
}
......@@ -5,6 +5,7 @@ import (
"errors"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
......@@ -23,11 +24,16 @@ func TestBalanceEnricher(t *testing.T) {
t.Run("GetBalanceSuccess", func(t *testing.T) {
enricher := NewBalanceEnricher()
caller := &mockGameCaller{balance: big.NewInt(84242), balanceAddr: common.Address{0xdd}}
caller := &mockGameCaller{
balance: big.NewInt(84242),
delayDuration: 3 * time.Hour,
balanceAddr: common.Address{0xdd},
}
game := &types.EnrichedGameData{}
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.NoError(t, err)
require.Equal(t, game.WETHContract, caller.balanceAddr)
require.Equal(t, game.ETHCollateral, caller.balance)
require.Equal(t, game.WETHDelay, caller.delayDuration)
})
}
......@@ -5,6 +5,7 @@ import (
"errors"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
......@@ -226,6 +227,7 @@ type mockGameCaller struct {
extraCredit []*big.Int
balanceErr error
balance *big.Int
delayDuration time.Duration
balanceAddr common.Address
withdrawalsCalls int
withdrawalsErr error
......@@ -290,11 +292,11 @@ func (m *mockGameCaller) GetCredits(_ context.Context, _ rpcblock.Block, recipie
return response, nil
}
func (m *mockGameCaller) GetBalance(_ context.Context, _ rpcblock.Block) (*big.Int, common.Address, error) {
func (m *mockGameCaller) GetBalanceAndDelay(_ context.Context, _ rpcblock.Block) (*big.Int, time.Duration, common.Address, error) {
if m.balanceErr != nil {
return nil, common.Address{}, m.balanceErr
return nil, 0, common.Address{}, m.balanceErr
}
return m.balance, m.balanceAddr, nil
return m.balance, m.delayDuration, m.balanceAddr, nil
}
func (m *mockGameCaller) IsResolved(_ context.Context, _ rpcblock.Block, claims ...faultTypes.Claim) ([]bool, error) {
......
......@@ -2,6 +2,7 @@ package types
import (
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
......@@ -43,6 +44,9 @@ type EnrichedGameData struct {
// The contract is potentially shared by multiple games.
WETHContract common.Address
// WETHDelay is the delay applied before credits can be withdrawn.
WETHDelay time.Duration
// ETHCollateral is the ETH balance of the (potentially shared) WETHContract
// This ETH balance will be used to pay out any bonds required by the games
// that use the same DelayedWETH contract.
......
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