Commit 813c74c5 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-dispute-mon: Report bond collateral metrics (#9791)

* op-dispute-mon: Add extractor for bond data

* op-dispute-mon: Implement transformer for bond collateral data

* op-dispute-mon: Report metrics for bond collateral.
parent 8c3a849d
...@@ -3,11 +3,13 @@ package metrics ...@@ -3,11 +3,13 @@ package metrics
import ( import (
"fmt" "fmt"
"io" "io"
"math/big"
"github.com/ethereum-optimism/optimism/op-service/sources/caching" "github.com/ethereum-optimism/optimism/op-service/sources/caching"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/ethereum-optimism/optimism/op-service/httputil" "github.com/ethereum-optimism/optimism/op-service/httputil"
...@@ -43,6 +45,8 @@ type Metricer interface { ...@@ -43,6 +45,8 @@ type Metricer interface {
RecordGamesStatus(inProgress, defenderWon, challengerWon int) RecordGamesStatus(inProgress, defenderWon, challengerWon int)
RecordGameAgreement(status GameAgreementStatus, count int) RecordGameAgreement(status GameAgreementStatus, count int)
RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int)
caching.Metrics caching.Metrics
} }
...@@ -65,6 +69,9 @@ type Metrics struct { ...@@ -65,6 +69,9 @@ type Metrics struct {
trackedGames prometheus.GaugeVec trackedGames prometheus.GaugeVec
gamesAgreement prometheus.GaugeVec gamesAgreement prometheus.GaugeVec
requiredCollateral prometheus.GaugeVec
availableCollateral prometheus.GaugeVec
} }
func (m *Metrics) Registry() *prometheus.Registry { func (m *Metrics) Registry() *prometheus.Registry {
...@@ -123,6 +130,26 @@ func NewMetrics() *Metrics { ...@@ -123,6 +130,26 @@ func NewMetrics() *Metrics {
"result_correctness", "result_correctness",
"root_agreement", "root_agreement",
}), }),
requiredCollateral: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "bond_collateral_required",
Help: "Required collateral (ETH) to cover outstanding bonds and credits",
}, []string{
// Address of the DelayedWETH contract in use. This is a limited set as only permissioned actors can deploy
// additional DelayedWETH contracts to be used by dispute games
"delayedWETH",
"balance",
}),
availableCollateral: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "bond_collateral_available",
Help: "Available collateral (ETH) to cover outstanding bonds and credits",
}, []string{
// Address of the DelayedWETH contract in use. This is a limited set as only permissioned actors can deploy
// additional DelayedWETH contracts to be used by dispute games
"delayedWETH",
"balance",
}),
} }
} }
...@@ -172,6 +199,15 @@ func (m *Metrics) RecordGameAgreement(status GameAgreementStatus, count int) { ...@@ -172,6 +199,15 @@ func (m *Metrics) RecordGameAgreement(status GameAgreementStatus, count int) {
m.gamesAgreement.WithLabelValues(labelValuesFor(status)...).Set(float64(count)) m.gamesAgreement.WithLabelValues(labelValuesFor(status)...).Set(float64(count))
} }
func (m *Metrics) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) {
balance := "sufficient"
if required.Cmp(available) > 0 {
balance = "insufficient"
}
m.requiredCollateral.WithLabelValues(addr.Hex(), balance).Set(weiToEther(required))
m.availableCollateral.WithLabelValues(addr.Hex(), balance).Set(weiToEther(available))
}
const ( const (
inProgress = true inProgress = true
correct = true correct = true
...@@ -217,3 +253,12 @@ func labelValuesFor(status GameAgreementStatus) []string { ...@@ -217,3 +253,12 @@ func labelValuesFor(status GameAgreementStatus) []string {
panic(fmt.Errorf("unknown game agreement status: %v", status)) panic(fmt.Errorf("unknown game agreement status: %v", status))
} }
} }
// weiToEther divides the wei value by 10^18 to get a number in ether as a float64
func weiToEther(wei *big.Int) float64 {
num := new(big.Rat).SetInt(wei)
denom := big.NewRat(params.Ether, 1)
num = num.Quo(num, denom)
f, _ := num.Float64()
return f
}
package metrics package metrics
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
type NoopMetricsImpl struct{} type NoopMetricsImpl struct{}
var NoopMetrics Metricer = new(NoopMetricsImpl) var NoopMetrics Metricer = new(NoopMetricsImpl)
...@@ -16,3 +22,5 @@ func (*NoopMetricsImpl) RecordOutputFetchTime(timestamp float64) {} ...@@ -16,3 +22,5 @@ func (*NoopMetricsImpl) RecordOutputFetchTime(timestamp float64) {}
func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {} func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {}
func (*NoopMetricsImpl) RecordGameAgreement(status GameAgreementStatus, count int) {} func (*NoopMetricsImpl) RecordGameAgreement(status GameAgreementStatus, count int) {}
func (i *NoopMetricsImpl) RecordBondCollateral(_ common.Address, _ *big.Int, _ *big.Int) {}
package bonds
import (
"math/big"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/transform"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
type BondMetrics interface {
RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int)
}
type Bonds struct {
logger log.Logger
metrics BondMetrics
}
func NewBonds(logger log.Logger, metrics BondMetrics) *Bonds {
return &Bonds{
logger: logger,
metrics: metrics,
}
}
func (b *Bonds) CheckBonds(games []*types.EnrichedGameData) {
data := transform.CalculateRequiredCollateral(games)
for addr, collateral := range data {
b.metrics.RecordBondCollateral(addr, collateral.Required, collateral.Actual)
}
}
package bonds
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/transform"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestCheckBonds(t *testing.T) {
weth1 := common.Address{0x1a}
weth1Balance := big.NewInt(4200)
weth2 := common.Address{0x2b}
weth2Balance := big.NewInt(6000)
game1 := &monTypes.EnrichedGameData{
Credits: map[common.Address]*big.Int{
common.Address{0x01}: big.NewInt(2),
},
WETHContract: weth1,
ETHCollateral: weth1Balance,
}
game2 := &monTypes.EnrichedGameData{
Credits: map[common.Address]*big.Int{
common.Address{0x01}: big.NewInt(46),
},
WETHContract: weth2,
ETHCollateral: weth2Balance,
}
logger := testlog.Logger(t, log.LvlInfo)
metrics := &stubBondMetrics{recorded: make(map[common.Address]transform.Collateral)}
bonds := NewBonds(logger, metrics)
bonds.CheckBonds([]*monTypes.EnrichedGameData{game1, game2})
require.Len(t, metrics.recorded, 2)
require.Contains(t, metrics.recorded, weth1)
require.Contains(t, metrics.recorded, weth2)
require.Equal(t, metrics.recorded[weth1].Required.Uint64(), uint64(2))
require.Equal(t, metrics.recorded[weth1].Actual.Uint64(), weth1Balance.Uint64())
require.Equal(t, metrics.recorded[weth2].Required.Uint64(), uint64(46))
require.Equal(t, metrics.recorded[weth2].Actual.Uint64(), weth2Balance.Uint64())
}
type stubBondMetrics struct {
recorded map[common.Address]transform.Collateral
}
func (s *stubBondMetrics) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) {
s.recorded[addr] = transform.Collateral{
Required: required,
Actual: available,
}
}
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
) )
type Forecast func(ctx context.Context, games []*types.EnrichedGameData) type Forecast func(ctx context.Context, games []*types.EnrichedGameData)
type Bonds func(games []*types.EnrichedGameData)
type BlockHashFetcher func(ctx context.Context, number *big.Int) (common.Hash, error) type BlockHashFetcher func(ctx context.Context, number *big.Int) (common.Hash, error)
type BlockNumberFetcher func(ctx context.Context) (uint64, error) type BlockNumberFetcher func(ctx context.Context) (uint64, error)
type Extract func(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*types.EnrichedGameData, error) type Extract func(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*types.EnrichedGameData, error)
...@@ -32,6 +33,7 @@ type gameMonitor struct { ...@@ -32,6 +33,7 @@ type gameMonitor struct {
delays RecordClaimResolutionDelayMax delays RecordClaimResolutionDelayMax
forecast Forecast forecast Forecast
bonds Bonds
extract Extract extract Extract
fetchBlockHash BlockHashFetcher fetchBlockHash BlockHashFetcher
fetchBlockNumber BlockNumberFetcher fetchBlockNumber BlockNumberFetcher
...@@ -45,6 +47,7 @@ func newGameMonitor( ...@@ -45,6 +47,7 @@ func newGameMonitor(
gameWindow time.Duration, gameWindow time.Duration,
delays RecordClaimResolutionDelayMax, delays RecordClaimResolutionDelayMax,
forecast Forecast, forecast Forecast,
bonds Bonds,
extract Extract, extract Extract,
fetchBlockNumber BlockNumberFetcher, fetchBlockNumber BlockNumberFetcher,
fetchBlockHash BlockHashFetcher, fetchBlockHash BlockHashFetcher,
...@@ -58,6 +61,7 @@ func newGameMonitor( ...@@ -58,6 +61,7 @@ func newGameMonitor(
gameWindow: gameWindow, gameWindow: gameWindow,
delays: delays, delays: delays,
forecast: forecast, forecast: forecast,
bonds: bonds,
extract: extract, extract: extract,
fetchBlockNumber: fetchBlockNumber, fetchBlockNumber: fetchBlockNumber,
fetchBlockHash: fetchBlockHash, fetchBlockHash: fetchBlockHash,
...@@ -92,6 +96,7 @@ func (m *gameMonitor) monitorGames() error { ...@@ -92,6 +96,7 @@ func (m *gameMonitor) monitorGames() error {
} }
m.delays(enrichedGames) m.delays(enrichedGames)
m.forecast(m.ctx, enrichedGames) m.forecast(m.ctx, enrichedGames)
m.bonds(enrichedGames)
return nil return nil
} }
......
...@@ -24,20 +24,20 @@ func TestMonitor_MinGameTimestamp(t *testing.T) { ...@@ -24,20 +24,20 @@ func TestMonitor_MinGameTimestamp(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("ZeroGameWindow", func(t *testing.T) { t.Run("ZeroGameWindow", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t) monitor, _, _, _, _ := setupMonitorTest(t)
monitor.gameWindow = time.Duration(0) monitor.gameWindow = time.Duration(0)
require.Equal(t, monitor.minGameTimestamp(), uint64(0)) require.Equal(t, monitor.minGameTimestamp(), uint64(0))
}) })
t.Run("ZeroClock", func(t *testing.T) { t.Run("ZeroClock", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t) monitor, _, _, _, _ := setupMonitorTest(t)
monitor.gameWindow = time.Minute monitor.gameWindow = time.Minute
monitor.clock = clock.NewDeterministicClock(time.Unix(0, 0)) monitor.clock = clock.NewDeterministicClock(time.Unix(0, 0))
require.Equal(t, uint64(0), monitor.minGameTimestamp()) require.Equal(t, uint64(0), monitor.minGameTimestamp())
}) })
t.Run("ValidArithmetic", func(t *testing.T) { t.Run("ValidArithmetic", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t) monitor, _, _, _, _ := setupMonitorTest(t)
monitor.gameWindow = time.Minute monitor.gameWindow = time.Minute
frozen := time.Unix(int64(time.Hour.Seconds()), 0) frozen := time.Unix(int64(time.Hour.Seconds()), 0)
monitor.clock = clock.NewDeterministicClock(frozen) monitor.clock = clock.NewDeterministicClock(frozen)
...@@ -50,7 +50,7 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -50,7 +50,7 @@ func TestMonitor_MonitorGames(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("FailedFetchBlocknumber", func(t *testing.T) { t.Run("FailedFetchBlocknumber", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t) monitor, _, _, _, _ := setupMonitorTest(t)
boom := errors.New("boom") boom := errors.New("boom")
monitor.fetchBlockNumber = func(ctx context.Context) (uint64, error) { monitor.fetchBlockNumber = func(ctx context.Context) (uint64, error) {
return 0, boom return 0, boom
...@@ -60,7 +60,7 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -60,7 +60,7 @@ func TestMonitor_MonitorGames(t *testing.T) {
}) })
t.Run("FailedFetchBlockHash", func(t *testing.T) { t.Run("FailedFetchBlockHash", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t) monitor, _, _, _, _ := setupMonitorTest(t)
boom := errors.New("boom") boom := errors.New("boom")
monitor.fetchBlockHash = func(ctx context.Context, number *big.Int) (common.Hash, error) { monitor.fetchBlockHash = func(ctx context.Context, number *big.Int) (common.Hash, error) {
return common.Hash{}, boom return common.Hash{}, boom
...@@ -70,21 +70,23 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -70,21 +70,23 @@ func TestMonitor_MonitorGames(t *testing.T) {
}) })
t.Run("MonitorsWithNoGames", func(t *testing.T) { t.Run("MonitorsWithNoGames", func(t *testing.T) {
monitor, factory, forecast, delays := setupMonitorTest(t) monitor, factory, forecast, delays, bonds := setupMonitorTest(t)
factory.games = []*monTypes.EnrichedGameData{} factory.games = []*monTypes.EnrichedGameData{}
err := monitor.monitorGames() err := monitor.monitorGames()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, forecast.calls) require.Equal(t, 1, forecast.calls)
require.Equal(t, 1, delays.calls) require.Equal(t, 1, delays.calls)
require.Equal(t, 1, bonds.calls)
}) })
t.Run("MonitorsMultipleGames", func(t *testing.T) { t.Run("MonitorsMultipleGames", func(t *testing.T) {
monitor, factory, forecast, delays := setupMonitorTest(t) monitor, factory, forecast, delays, bonds := setupMonitorTest(t)
factory.games = []*monTypes.EnrichedGameData{{}, {}, {}} factory.games = []*monTypes.EnrichedGameData{{}, {}, {}}
err := monitor.monitorGames() err := monitor.monitorGames()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, forecast.calls) require.Equal(t, 1, forecast.calls)
require.Equal(t, 1, delays.calls) require.Equal(t, 1, delays.calls)
require.Equal(t, 1, bonds.calls)
}) })
} }
...@@ -92,7 +94,7 @@ func TestMonitor_StartMonitoring(t *testing.T) { ...@@ -92,7 +94,7 @@ func TestMonitor_StartMonitoring(t *testing.T) {
t.Run("MonitorsGames", func(t *testing.T) { t.Run("MonitorsGames", func(t *testing.T) {
addr1 := common.Address{0xaa} addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb} addr2 := common.Address{0xbb}
monitor, factory, forecaster, _ := setupMonitorTest(t) monitor, factory, forecaster, _, _ := setupMonitorTest(t)
factory.games = []*monTypes.EnrichedGameData{newEnrichedGameData(addr1, 9999), newEnrichedGameData(addr2, 9999)} factory.games = []*monTypes.EnrichedGameData{newEnrichedGameData(addr1, 9999), newEnrichedGameData(addr2, 9999)}
factory.maxSuccess = len(factory.games) // Only allow two successful fetches factory.maxSuccess = len(factory.games) // Only allow two successful fetches
...@@ -105,7 +107,7 @@ func TestMonitor_StartMonitoring(t *testing.T) { ...@@ -105,7 +107,7 @@ func TestMonitor_StartMonitoring(t *testing.T) {
}) })
t.Run("FailsToFetchGames", func(t *testing.T) { t.Run("FailsToFetchGames", func(t *testing.T) {
monitor, factory, forecaster, _ := setupMonitorTest(t) monitor, factory, forecaster, _, _ := setupMonitorTest(t)
factory.fetchErr = errors.New("boom") factory.fetchErr = errors.New("boom")
monitor.StartMonitoring() monitor.StartMonitoring()
...@@ -127,7 +129,7 @@ func newEnrichedGameData(proxy common.Address, timestamp uint64) *monTypes.Enric ...@@ -127,7 +129,7 @@ func newEnrichedGameData(proxy common.Address, timestamp uint64) *monTypes.Enric
} }
} }
func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast, *mockDelayCalculator) { func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast, *mockDelayCalculator, *mockBonds) {
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
fetchBlockNum := func(ctx context.Context) (uint64, error) { fetchBlockNum := func(ctx context.Context) (uint64, error) {
return 1, nil return 1, nil
...@@ -140,6 +142,7 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast ...@@ -140,6 +142,7 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast
cl.Start() cl.Start()
extractor := &mockExtractor{} extractor := &mockExtractor{}
forecast := &mockForecast{} forecast := &mockForecast{}
bonds := &mockBonds{}
delays := &mockDelayCalculator{} delays := &mockDelayCalculator{}
monitor := newGameMonitor( monitor := newGameMonitor(
context.Background(), context.Background(),
...@@ -149,11 +152,12 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast ...@@ -149,11 +152,12 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast
10*time.Second, 10*time.Second,
delays.RecordClaimResolutionDelayMax, delays.RecordClaimResolutionDelayMax,
forecast.Forecast, forecast.Forecast,
bonds.CheckBonds,
extractor.Extract, extractor.Extract,
fetchBlockNum, fetchBlockNum,
fetchBlockHash, fetchBlockHash,
) )
return monitor, extractor, forecast, delays return monitor, extractor, forecast, delays, bonds
} }
type mockDelayCalculator struct { type mockDelayCalculator struct {
...@@ -172,6 +176,14 @@ func (m *mockForecast) Forecast(ctx context.Context, games []*monTypes.EnrichedG ...@@ -172,6 +176,14 @@ func (m *mockForecast) Forecast(ctx context.Context, games []*monTypes.EnrichedG
m.calls++ m.calls++
} }
type mockBonds struct {
calls int
}
func (m *mockBonds) CheckBonds(_ []*monTypes.EnrichedGameData) {
m.calls++
}
type mockExtractor struct { type mockExtractor struct {
fetchErr error fetchErr error
calls int calls int
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/bonds"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -39,6 +40,7 @@ type Service struct { ...@@ -39,6 +40,7 @@ type Service struct {
delays *resolution.DelayCalculator delays *resolution.DelayCalculator
extractor *extract.Extractor extractor *extract.Extractor
forecast *forecast forecast *forecast
bonds *bonds.Bonds
game *extract.GameCallerCreator game *extract.GameCallerCreator
rollupClient *sources.RollupClient rollupClient *sources.RollupClient
validator *outputValidator validator *outputValidator
...@@ -90,6 +92,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error ...@@ -90,6 +92,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
s.initExtractor() s.initExtractor()
s.initForecast(cfg) s.initForecast(cfg)
s.initBonds()
s.initMonitor(ctx, cfg) // Monitor must be initialized last s.initMonitor(ctx, cfg) // Monitor must be initialized last
...@@ -122,6 +125,10 @@ func (s *Service) initForecast(cfg *config.Config) { ...@@ -122,6 +125,10 @@ func (s *Service) initForecast(cfg *config.Config) {
s.forecast = newForecast(s.logger, s.metrics, s.validator) s.forecast = newForecast(s.logger, s.metrics, s.validator)
} }
func (s *Service) initBonds() {
s.bonds = bonds.NewBonds(s.logger, s.metrics)
}
func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config) error { func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config) error {
outputRollupClient, err := dial.DialRollupClientWithTimeout(ctx, dial.DefaultDialTimeout, s.logger, cfg.RollupRpc) outputRollupClient, err := dial.DialRollupClientWithTimeout(ctx, dial.DefaultDialTimeout, s.logger, cfg.RollupRpc)
if err != nil { if err != nil {
...@@ -201,6 +208,7 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) { ...@@ -201,6 +208,7 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) {
cfg.GameWindow, cfg.GameWindow,
s.delays.RecordClaimResolutionDelayMax, s.delays.RecordClaimResolutionDelayMax,
s.forecast.Forecast, s.forecast.Forecast,
s.bonds.CheckBonds,
s.extractor.Extract, s.extractor.Extract,
s.l1Client.BlockNumber, s.l1Client.BlockNumber,
blockHashFetcher, blockHashFetcher,
......
package transform
import (
"math/big"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum/go-ethereum/common"
)
type Collateral struct {
// Required is the amount of collateral required to pay out bonds.
Required *big.Int
// Actual is the amount of collateral actually head by the DelayedWETH contract
Actual *big.Int
}
// CalculateRequiredCollateral determines the minimum balance required for each DelayedWETH contract used by a set
// of dispute games.
// Returns a map of DelayedWETH contract address to collateral data (required and actual amounts)
func CalculateRequiredCollateral(games []*monTypes.EnrichedGameData) map[common.Address]Collateral {
result := make(map[common.Address]Collateral)
for _, game := range games {
collateral, ok := result[game.WETHContract]
if !ok {
collateral = Collateral{
Required: big.NewInt(0),
Actual: game.ETHCollateral,
}
}
gameRequired := requiredCollateralForGame(game)
collateral.Required = new(big.Int).Add(collateral.Required, gameRequired)
result[game.WETHContract] = collateral
}
return result
}
func requiredCollateralForGame(game *monTypes.EnrichedGameData) *big.Int {
required := big.NewInt(0)
for _, claim := range game.Claims {
if monTypes.ResolvedBondAmount.Cmp(claim.Bond) != 0 {
required = new(big.Int).Add(required, claim.Bond)
}
}
for _, unclaimedCredit := range game.Credits {
required = new(big.Int).Add(required, unclaimedCredit)
}
return required
}
package transform
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestCalculateRequiredCollateral(t *testing.T) {
weth1 := common.Address{0x1a}
weth1Balance := big.NewInt(4200)
weth2 := common.Address{0x2b}
weth2Balance := big.NewInt(6000)
game1 := &monTypes.EnrichedGameData{
Claims: []types.Claim{
{
ClaimData: types.ClaimData{
Bond: monTypes.ResolvedBondAmount,
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(7),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
},
Credits: map[common.Address]*big.Int{
common.Address{0x01}: big.NewInt(2),
common.Address{0x04}: big.NewInt(3),
},
WETHContract: weth1,
ETHCollateral: weth1Balance,
}
game2 := &monTypes.EnrichedGameData{
Claims: []types.Claim{
{
ClaimData: types.ClaimData{
Bond: monTypes.ResolvedBondAmount,
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(6),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(9),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
},
Credits: map[common.Address]*big.Int{
common.Address{0x01}: big.NewInt(4),
common.Address{0x04}: big.NewInt(1),
},
WETHContract: weth1,
ETHCollateral: weth1Balance,
}
game3 := &monTypes.EnrichedGameData{
Claims: []types.Claim{
{
ClaimData: types.ClaimData{
Bond: big.NewInt(23),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
},
Credits: map[common.Address]*big.Int{
common.Address{0x01}: big.NewInt(46),
},
WETHContract: weth2,
ETHCollateral: weth2Balance,
}
actual := CalculateRequiredCollateral([]*monTypes.EnrichedGameData{game1, game2, game3})
require.Len(t, actual, 2)
require.Contains(t, actual, weth1)
require.Contains(t, actual, weth2)
require.Equal(t, actual[weth1].Required.Uint64(), uint64(5+7+2+3+6+9+4+1))
require.Equal(t, actual[weth1].Actual.Uint64(), weth1Balance.Uint64())
require.Equal(t, actual[weth2].Required.Uint64(), uint64(23+46))
require.Equal(t, actual[weth2].Actual.Uint64(), weth2Balance.Uint64())
}
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