Commit d0a63191 authored by clabby's avatar clabby Committed by GitHub

feat(ctb): Clock extensions (#10182)

* feat(ctb): Clock extensions

* Add configurable clock extension immutable

locks,bindings,etc.

comment fixes

x

* `clockExtension` config checks

* feat(ctb): Enforce max supported depth in `FaultDisputeGame` constructor

## Overview

Enforces the max supported depth of the `LibPosition` library's
implemented functionality on the `Position` type in the
`FaultDisputeGame`'s constructor

* use clock dur * 2 in bond monitor
parent 34a44375
......@@ -130,7 +130,7 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False):
deploy_config['l1GenesisBlockTimestamp'] = '{:#x}'.format(int(time.time()))
if DEVNET_FPAC:
deploy_config['useFaultProofs'] = True
deploy_config['faultGameMaxDuration'] = 10
deploy_config['faultGameMaxClockDuration'] = 10
deploy_config['faultGameWithdrawalDelay'] = 0
if DEVNET_PLASMA:
deploy_config['usePlasma'] = True
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -210,10 +210,12 @@ type DeployConfig struct {
// supports. Ideally, this should be conservatively set so that there is always enough
// room for a full Cannon trace.
FaultGameMaxDepth uint64 `json:"faultGameMaxDepth"`
// FaultGameMaxDuration is the maximum amount of time (in seconds) that the fault dispute
// game can run for before it is ready to be resolved. Each side receives half of this value
// on their chess clock at the inception of the dispute.
FaultGameMaxDuration uint64 `json:"faultGameMaxDuration"`
// FaultGameClockExtension is the amount of time that the dispute game will set the potential grandchild claim's,
// clock to, if the remaining time is less than this value at the time of a claim's creation.
FaultGameClockExtension uint64 `json:"faultGameClockExtension"`
// FaultGameMaxClockDuration is the maximum amount of time that may accumulate on a team's chess clock before they
// may no longer respond.
FaultGameMaxClockDuration uint64 `json:"faultGameMaxClockDuration"`
// FaultGameGenesisBlock is the block number for genesis.
FaultGameGenesisBlock uint64 `json:"faultGameGenesisBlock"`
// FaultGameGenesisOutputRoot is the output root for the genesis block.
......
......@@ -69,7 +69,8 @@
"fundDevAccounts": true,
"faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameMaxDepth": 63,
"faultGameMaxDuration": 604800,
"faultGameClockExtension": 10800,
"faultGameMaxClockDuration": 302400,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameSplitDepth": 0,
......
......@@ -42,7 +42,7 @@ type Agent struct {
selective bool
claimants []common.Address
maxDepth types.Depth
gameDuration time.Duration
maxClockDuration time.Duration
log log.Logger
}
......@@ -52,7 +52,7 @@ func NewAgent(
l1Clock types.ClockReader,
loader ClaimLoader,
maxDepth types.Depth,
gameDuration time.Duration,
maxClockDuration time.Duration,
trace types.TraceAccessor,
responder Responder,
log log.Logger,
......@@ -69,7 +69,7 @@ func NewAgent(
selective: selective,
claimants: claimants,
maxDepth: maxDepth,
gameDuration: gameDuration,
maxClockDuration: maxClockDuration,
log: log,
}
}
......@@ -163,11 +163,10 @@ func (a *Agent) tryResolveClaims(ctx context.Context) error {
if len(claims) == 0 {
return errNoResolvableClaims
}
maxChessTime := a.gameDuration / 2
var resolvableClaims []uint64
for _, claim := range claims {
if claim.ChessTime(a.l1Clock.Now()) <= maxChessTime {
if claim.ChessTime(a.l1Clock.Now()) <= a.maxClockDuration {
continue
}
if a.selective {
......
......@@ -139,7 +139,7 @@ func TestSkipAttemptingToResolveClaimsWhenClockNotExpired(t *testing.T) {
claimBuilder := test.NewClaimBuilder(t, depth, alphabet.NewTraceProvider(big.NewInt(0), depth))
claimLoader.claims = []types.Claim{
claimBuilder.CreateRootClaim(test.WithExpiredClock(agent.gameDuration)),
claimBuilder.CreateRootClaim(test.WithExpiredClock(agent.maxClockDuration)),
}
require.NoError(t, agent.Act(context.Background()))
......@@ -170,7 +170,7 @@ func setupTestAgent(t *testing.T) (*Agent, *stubClaimLoader, *stubResponder) {
logger := testlog.Logger(t, log.LevelInfo)
claimLoader := &stubClaimLoader{}
depth := types.Depth(4)
gameDuration := 6 * time.Minute
gameDuration := 3 * time.Minute
provider := alphabet.NewTraceProvider(big.NewInt(0), depth)
responder := &stubResponder{}
systemClock := clock.NewDeterministicClock(time.UnixMilli(120200))
......
......@@ -19,7 +19,7 @@ import (
)
var (
methodGameDuration = "gameDuration"
methodMaxClockDuration = "maxClockDuration"
methodMaxGameDepth = "maxGameDepth"
methodAbsolutePrestate = "absolutePrestate"
methodStatus = "status"
......@@ -107,7 +107,7 @@ func (f *FaultDisputeGameContract) GetBlockRange(ctx context.Context) (prestateB
return
}
// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and game duration.
// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and max clock duration.
func (f *FaultDisputeGameContract) GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) {
defer f.metrics.StartContractRequest("GetGameMetadata")()
results, err := f.multiCaller.Call(ctx, block,
......@@ -115,7 +115,7 @@ func (f *FaultDisputeGameContract) GetGameMetadata(ctx context.Context, block rp
f.contract.Call(methodL2BlockNumber),
f.contract.Call(methodRootClaim),
f.contract.Call(methodStatus),
f.contract.Call(methodGameDuration))
f.contract.Call(methodMaxClockDuration))
if err != nil {
return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to retrieve game metadata: %w", err)
}
......@@ -274,11 +274,11 @@ func (f *FaultDisputeGameContract) GetOracle(ctx context.Context) (*PreimageOrac
return vm.Oracle(ctx)
}
func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (time.Duration, error) {
defer f.metrics.StartContractRequest("GetGameDuration")()
result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodGameDuration))
func (f *FaultDisputeGameContract) GetMaxClockDuration(ctx context.Context) (time.Duration, error) {
defer f.metrics.StartContractRequest("GetMaxClockDuration")()
result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodMaxClockDuration))
if err != nil {
return 0, fmt.Errorf("failed to fetch game duration: %w", err)
return 0, fmt.Errorf("failed to fetch max clock duration: %w", err)
}
return time.Duration(result.GetUint64(0)) * time.Second, nil
}
......
......@@ -44,12 +44,12 @@ func TestSimpleGetters(t *testing.T) {
},
},
{
methodAlias: "gameDuration",
method: methodGameDuration,
methodAlias: "maxClockDuration",
method: methodMaxClockDuration,
result: uint64(5566),
expected: 5566 * time.Second,
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetGameDuration(context.Background())
return game.GetMaxClockDuration(context.Background())
},
},
{
......@@ -341,7 +341,7 @@ func TestGetGameMetadata(t *testing.T) {
stubRpc, contract := setupFaultDisputeGameTest(t)
expectedL1Head := common.Hash{0x0a, 0x0b}
expectedL2BlockNumber := uint64(123)
expectedGameDuration := uint64(456)
expectedMaxClockDuration := uint64(456)
expectedRootClaim := common.Hash{0x01, 0x02}
expectedStatus := types.GameStatusChallengerWon
block := rpcblock.ByNumber(889)
......@@ -349,14 +349,14 @@ func TestGetGameMetadata(t *testing.T) {
stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)})
stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim})
stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus})
stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedGameDuration})
stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration})
l1Head, l2BlockNumber, rootClaim, status, duration, err := contract.GetGameMetadata(context.Background(), block)
require.NoError(t, err)
require.Equal(t, expectedL1Head, l1Head)
require.Equal(t, expectedL2BlockNumber, l2BlockNumber)
require.Equal(t, expectedRootClaim, rootClaim)
require.Equal(t, expectedStatus, status)
require.Equal(t, expectedGameDuration, duration)
require.Equal(t, expectedMaxClockDuration, duration)
}
func TestGetStartingRootHash(t *testing.T) {
......
......@@ -59,7 +59,7 @@ type GameContract interface {
ClaimLoader
GetStatus(ctx context.Context) (gameTypes.GameStatus, error)
GetMaxGameDepth(ctx context.Context) (types.Depth, error)
GetGameDuration(ctx context.Context) (time.Duration, error)
GetMaxClockDuration(ctx context.Context) (time.Duration, error)
GetOracle(ctx context.Context) (*contracts.PreimageOracleContract, error)
GetL1Head(ctx context.Context) (common.Hash, error)
}
......@@ -104,7 +104,7 @@ func NewGamePlayer(
}, nil
}
gameDuration, err := loader.GetGameDuration(ctx)
maxClockDuration, err := loader.GetMaxClockDuration(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game duration: %w", err)
}
......@@ -146,7 +146,7 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
agent := NewAgent(m, systemClock, l1Clock, loader, gameDepth, gameDuration, accessor, responder, logger, selective, claimants)
agent := NewAgent(m, systemClock, l1Clock, loader, gameDepth, maxClockDuration, accessor, responder, logger, selective, claimants)
return &GamePlayer{
act: agent.Act,
loader: loader,
......
......@@ -63,9 +63,10 @@ func WithParent(claim types.Claim) ClaimOpt {
cfg.parentIdx = claim.ContractIndex
})
}
func WithExpiredClock(gameDuration time.Duration) ClaimOpt {
func WithExpiredClock(maxClockDuration time.Duration) ClaimOpt {
return claimOptFn(func(cfg *claimCfg) {
cfg.clockDuration = gameDuration / 2
cfg.clockDuration = maxClockDuration
})
}
......
......@@ -47,7 +47,7 @@ func (b *Bonds) CheckBonds(games []*types.EnrichedGameData) {
func (b *Bonds) checkCredits(game *types.EnrichedGameData) {
// Check if the max duration has been reached for this game
duration := uint64(b.clock.Now().Unix()) - game.Timestamp
maxDurationReached := duration >= game.Duration
maxDurationReached := duration >= game.MaxClockDuration*2
// Iterate over claims and filter out resolved ones
recipients := make(map[int]common.Address)
......
......@@ -60,7 +60,7 @@ func (c *ClaimMonitor) checkGameClaims(
) {
// Check if the game is in the first half
duration := uint64(c.clock.Now().Unix()) - game.Timestamp
firstHalf := duration <= (game.Duration / 2)
firstHalf := duration <= game.MaxClockDuration
// Iterate over the game's claims
for _, claim := range game.Claims {
......@@ -74,7 +74,7 @@ func (c *ClaimMonitor) checkGameClaims(
c.logger.Error("Claim resolved in the first half of the game duration", "game", game.Proxy, "claimContractIndex", claim.ContractIndex)
}
maxChessTime := time.Duration(game.Duration/2) * time.Second
maxChessTime := time.Duration(game.MaxClockDuration) * time.Second
accumulatedTime := claim.ChessTime(c.clock.Now())
clockExpired := accumulatedTime >= maxChessTime
......
......@@ -53,8 +53,8 @@ func newTestClaimMonitor(t *testing.T) (*ClaimMonitor, *clock.DeterministicClock
cl := clock.NewDeterministicClock(frozen)
metrics := &stubClaimMetrics{}
honestActors := []common.Address{
common.Address{0x01},
common.Address{0x02},
{0x01},
{0x02},
}
return NewClaimMonitor(logger, cl, honestActors, metrics), cl, metrics
}
......@@ -87,11 +87,11 @@ func makeMultipleTestGames(duration uint64) []*types.EnrichedGameData {
func makeTestGame(duration uint64) *types.EnrichedGameData {
return &types.EnrichedGameData{
Duration: duration,
MaxClockDuration: duration / 2,
Recipients: map[common.Address]bool{
common.Address{0x02}: true,
common.Address{0x03}: true,
common.Address{0x04}: true,
{0x02}: true,
{0x03}: true,
{0x04}: true,
},
Claims: []types.EnrichedClaim{
{
......
......@@ -12,8 +12,10 @@ import (
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
)
type CreateGameCaller func(game gameTypes.GameMetadata) (GameCaller, error)
type FactoryGameFetcher func(ctx context.Context, blockHash common.Hash, earliestTimestamp uint64) ([]gameTypes.GameMetadata, error)
type (
CreateGameCaller func(game gameTypes.GameMetadata) (GameCaller, error)
FactoryGameFetcher func(ctx context.Context, blockHash common.Hash, earliestTimestamp uint64) ([]gameTypes.GameMetadata, error)
)
type Enricher interface {
Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error
......@@ -71,7 +73,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game
L2BlockNumber: l2BlockNum,
RootClaim: rootClaim,
Status: status,
Duration: duration,
MaxClockDuration: duration,
Claims: enrichedClaims,
}
if err := e.applyEnrichers(ctx, blockHash, caller, enrichedGame); err != nil {
......
......@@ -34,16 +34,16 @@ func (d *DelayCalculator) RecordClaimResolutionDelayMax(games []*types.EnrichedG
func (d *DelayCalculator) getMaxResolutionDelay(game *types.EnrichedGameData) uint64 {
var maxDelay uint64 = 0
for _, claim := range game.Claims {
maxDelay = max(d.getOverflowTime(game.Duration, &claim), maxDelay)
maxDelay = max(d.getOverflowTime(game.MaxClockDuration, &claim), maxDelay)
}
return maxDelay
}
func (d *DelayCalculator) getOverflowTime(maxGameDuration uint64, claim *types.EnrichedClaim) uint64 {
func (d *DelayCalculator) getOverflowTime(maxClockDuration uint64, claim *types.EnrichedClaim) uint64 {
if claim.Resolved {
return 0
}
maxChessTime := time.Duration(maxGameDuration/2) * time.Second
maxChessTime := time.Duration(maxClockDuration) * time.Second
accumulatedTime := claim.ChessTime(d.clock.Now())
if accumulatedTime < maxChessTime {
return 0
......
......@@ -12,7 +12,7 @@ import (
)
var (
maxGameDuration = uint64(960)
maxClockDuration = uint64(480)
frozen = time.Unix(int64(time.Hour.Seconds()), 0)
)
......@@ -22,7 +22,7 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
claim := &monTypes.EnrichedClaim{
Resolved: true,
}
delay := d.getOverflowTime(maxGameDuration, claim)
delay := d.getOverflowTime(maxClockDuration, claim)
require.Equal(t, uint64(0), delay)
require.Equal(t, 0, metrics.calls)
})
......@@ -39,14 +39,14 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
Clock: types.NewClock(duration, timestamp),
},
}
delay := d.getOverflowTime(maxGameDuration, claim)
delay := d.getOverflowTime(maxClockDuration, claim)
require.Equal(t, uint64(0), delay)
require.Equal(t, 0, metrics.calls)
})
t.Run("OverflowTime", func(t *testing.T) {
d, metrics, cl := setupDelayCalculatorTest(t)
duration := time.Duration(maxGameDuration/2) * time.Second
duration := time.Duration(maxClockDuration) * time.Second
timestamp := cl.Now().Add(4 * -time.Minute)
claim := &monTypes.EnrichedClaim{
Claim: types.Claim{
......@@ -56,7 +56,7 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
Clock: types.NewClock(duration, timestamp),
},
}
delay := d.getOverflowTime(maxGameDuration, claim)
delay := d.getOverflowTime(maxClockDuration, claim)
require.Equal(t, uint64(240), delay)
require.Equal(t, 0, metrics.calls)
})
......@@ -80,7 +80,7 @@ func TestDelayCalculator_getMaxResolutionDelay(t *testing.T) {
d, metrics, _ := setupDelayCalculatorTest(t)
game := &monTypes.EnrichedGameData{
Claims: test.claims,
Duration: maxGameDuration,
MaxClockDuration: maxClockDuration,
}
got := d.getMaxResolutionDelay(game)
require.Equal(t, 0, metrics.calls)
......@@ -122,22 +122,22 @@ func createGameWithClaimsList() []*monTypes.EnrichedGameData {
return []*monTypes.EnrichedGameData{
{
Claims: createClaimList()[:1],
Duration: maxGameDuration,
MaxClockDuration: maxClockDuration,
},
{
Claims: createClaimList()[:2],
Duration: maxGameDuration,
MaxClockDuration: maxClockDuration,
},
{
Claims: createClaimList(),
Duration: maxGameDuration,
MaxClockDuration: maxClockDuration,
},
}
}
func createClaimList() []monTypes.EnrichedClaim {
newClock := func(multiplier int) types.Clock {
duration := maxGameDuration / 2
duration := maxClockDuration
timestamp := frozen.Add(-time.Minute * time.Duration(multiplier))
return types.NewClock(time.Duration(duration)*time.Second, timestamp)
}
......
......@@ -52,7 +52,7 @@ func (r *ResolutionMonitor) CheckResolutions(games []*types.EnrichedGameData) {
for _, game := range games {
complete := game.Status != gameTypes.GameStatusInProgress
duration := uint64(r.clock.Now().Unix()) - game.Timestamp
maxDurationReached := duration >= game.Duration
maxDurationReached := duration >= game.MaxClockDuration
status.Inc(complete, maxDurationReached)
}
r.metrics.RecordGameResolutionStatus(true, true, status.completeMaxDuration)
......
......@@ -45,7 +45,7 @@ func (s *stubResolutionMetrics) RecordGameResolutionStatus(complete bool, maxDur
func newTestGames(duration uint64) []*types.EnrichedGameData {
newTestGame := func(duration uint64, status gameTypes.GameStatus) *types.EnrichedGameData {
return &types.EnrichedGameData{Duration: duration, Status: status}
return &types.EnrichedGameData{MaxClockDuration: duration, Status: status}
}
return []*types.EnrichedGameData{
newTestGame(duration, gameTypes.GameStatusInProgress),
......
......@@ -22,7 +22,7 @@ type EnrichedGameData struct {
L2BlockNumber uint64
RootClaim common.Hash
Status types.GameStatus
Duration uint64
MaxClockDuration uint64
Claims []EnrichedClaim
// Recipients maps addresses to true if they are a bond recipient in the game.
......
......@@ -564,7 +564,7 @@ func (s *CrossLayerUser) ResolveClaim(t Testing, l2TxHash common.Hash) common.Ha
game, err := s.getDisputeGame(t, *params)
require.NoError(t, err)
expiry, err := game.GameDuration(&bind.CallOpts{})
expiry, err := game.MaxClockDuration(&bind.CallOpts{})
require.Nil(t, err)
time.Sleep(time.Duration(expiry) * time.Second)
......
......@@ -32,9 +32,9 @@ func (g *FaultGameHelper) Addr() common.Address {
return g.addr
}
func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
duration, err := g.game.GameDuration(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to get game duration")
func (g *FaultGameHelper) MaxClockDuration(ctx context.Context) time.Duration {
duration, err := g.game.MaxClockDuration(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to get max clock duration")
return time.Duration(duration) * time.Second
}
......
......@@ -161,9 +161,9 @@ func (g *OutputGameHelper) correctOutputRoot(ctx context.Context, pos types.Posi
return outputRoot
}
func (g *OutputGameHelper) GameDuration(ctx context.Context) time.Duration {
duration, err := g.game.GameDuration(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to get game duration")
func (g *OutputGameHelper) MaxClockDuration(ctx context.Context) time.Duration {
duration, err := g.game.MaxClockDuration(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to get max clock duration")
return time.Duration(duration) * time.Second
}
......
......@@ -67,7 +67,7 @@ func TestOutputAlphabetGame_ChallengerWins(t *testing.T) {
claim.WaitForCountered(ctx)
game.LogGameData(ctx)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
......@@ -109,7 +109,7 @@ func TestOutputAlphabetGame_ReclaimBond(t *testing.T) {
balance = game.WethBalance(ctx, game.Addr())
require.Truef(t, balance.Cmp(big.NewInt(0)) > 0, "Expected game balance to be above zero")
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
......@@ -160,7 +160,7 @@ func TestOutputAlphabetGame_ValidOutputRoot(t *testing.T) {
game.LogGameData(ctx)
}
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusDefenderWins)
}
......@@ -210,7 +210,7 @@ func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) {
// Wait for 4 blocks of no challenger responses. The challenger may still be stepping on invalid claims at max depth
game.WaitForInactivity(ctx, 4, false)
gameDuration := game.GameDuration(ctx)
gameDuration := game.MaxClockDuration(ctx)
sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
......@@ -287,7 +287,7 @@ func TestOutputAlphabetGame_FreeloaderEarnsNothing(t *testing.T) {
}
game.LogGameData(ctx)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusDefenderWins)
......
......@@ -68,7 +68,7 @@ func TestOutputCannonGame(t *testing.T) {
claim.WaitForCountered(ctx)
game.LogGameData(ctx)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
}
......@@ -96,7 +96,7 @@ func TestOutputCannon_ChallengeAllZeroClaim(t *testing.T) {
game.LogGameData(ctx)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
......@@ -171,7 +171,7 @@ func TestOutputCannonDisputeGame(t *testing.T) {
}
})
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.LogGameData(ctx)
......@@ -208,7 +208,7 @@ func TestOutputCannonDefendStep(t *testing.T) {
}
})
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
......@@ -426,7 +426,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) {
})
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
......@@ -493,7 +493,7 @@ func TestOutputCannonPoisonedPostState(t *testing.T) {
claimToIgnore2.RequireOnlyCounteredBy(ctx /* nothing */)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.LogGameData(ctx)
......@@ -543,7 +543,7 @@ func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot(t *testing.T) {
correctTrace.StepClaimFails(ctx, claim, false)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusDefenderWins)
......@@ -594,7 +594,7 @@ func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot(t *testing.T) {
claim.WaitForCountered(ctx)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
......@@ -654,7 +654,7 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) {
claim.WaitForCountered(ctx)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
......@@ -714,7 +714,7 @@ func TestInvalidateUnsafeProposal(t *testing.T) {
})
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
......@@ -776,7 +776,7 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) {
})
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
......
......@@ -200,7 +200,7 @@ func FinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Clie
proxy, err := bindings.NewFaultDisputeGame(game.DisputeGameProxy, l1Client)
require.Nil(t, err)
expiry, err := proxy.GameDuration(&bind.CallOpts{})
expiry, err := proxy.MaxClockDuration(&bind.CallOpts{})
require.Nil(t, err)
time.Sleep(time.Duration(expiry) * time.Second)
......
......@@ -51,7 +51,8 @@
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 50,
"faultGameMaxDuration": 2400,
"faultGameClockExtension": 0,
"faultGameMaxClockDuration": 1200,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
"faultGameSplitDepth": 14,
......
......@@ -45,7 +45,8 @@
"fundDevAccounts": false,
"faultGameAbsolutePrestate": "0x03e1255457128b9afd9acf93239c1d477bdff88624901f9ca8fe0783b756dbe0",
"faultGameMaxDepth": 73,
"faultGameMaxDuration": 604800,
"faultGameClockExtension": 10800,
"faultGameMaxClockDuration": 302400,
"faultGameGenesisBlock": 4061224,
"faultGameGenesisOutputRoot": "0xd08055c58b2c5149565c636b44fad2c25b5ccddef1385a2cb721529d7480b242",
"faultGameSplitDepth": 32,
......
......@@ -47,7 +47,8 @@
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameMaxDepth": 8,
"faultGameMaxDuration": 2400,
"faultGameClockExtension": 0,
"faultGameMaxClockDuration": 1200,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
"faultGameSplitDepth": 4,
......
......@@ -70,7 +70,8 @@
"systemConfigStartBlock": 4071248,
"faultGameAbsolutePrestate": "0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808",
"faultGameMaxDepth": 73,
"faultGameMaxDuration": 604800,
"faultGameClockExtension": 10800,
"faultGameMaxClockDuration": 302400,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0x91bd00ecd596a86c9f4e12c0646578e77022881c87c06ec6aa31e656d2730688",
"faultGameSplitDepth": 30,
......
......@@ -45,7 +45,8 @@
"fundDevAccounts": false,
"faultGameAbsolutePrestate": "0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808",
"faultGameMaxDepth": 73,
"faultGameMaxDuration": 604800,
"faultGameClockExtension": 10800,
"faultGameMaxClockDuration": 302400,
"faultGameGenesisBlock": 9496192,
"faultGameGenesisOutputRoot": "0x63b1cda487c072b020a57c1203f7c2921754005cadbd54bed7f558111b8278d8",
"faultGameSplitDepth": 30,
......
......@@ -1363,7 +1363,8 @@ contract Deploy is Deployer {
_absolutePrestate: _params.absolutePrestate,
_maxGameDepth: _params.maxGameDepth,
_splitDepth: cfg.faultGameSplitDepth(),
_gameDuration: Duration.wrap(uint64(cfg.faultGameMaxDuration())),
_clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())),
_maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())),
_vm: _params.faultVm,
_weth: _params.weth,
_anchorStateRegistry: _params.anchorStateRegistry,
......@@ -1378,7 +1379,8 @@ contract Deploy is Deployer {
_absolutePrestate: _params.absolutePrestate,
_maxGameDepth: _params.maxGameDepth,
_splitDepth: cfg.faultGameSplitDepth(),
_gameDuration: Duration.wrap(uint64(cfg.faultGameMaxDuration())),
_clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())),
_maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())),
_vm: _params.faultVm,
_weth: _params.weth,
_anchorStateRegistry: _params.anchorStateRegistry,
......
......@@ -55,7 +55,8 @@ contract DeployConfig is Script {
bytes32 public faultGameGenesisOutputRoot;
uint256 public faultGameMaxDepth;
uint256 public faultGameSplitDepth;
uint256 public faultGameMaxDuration;
uint256 public faultGameClockExtension;
uint256 public faultGameMaxClockDuration;
uint256 public faultGameWithdrawalDelay;
uint256 public preimageOracleMinProposalSize;
uint256 public preimageOracleChallengePeriod;
......@@ -128,7 +129,8 @@ contract DeployConfig is Script {
faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate");
faultGameMaxDepth = stdJson.readUint(_json, "$.faultGameMaxDepth");
faultGameSplitDepth = stdJson.readUint(_json, "$.faultGameSplitDepth");
faultGameMaxDuration = stdJson.readUint(_json, "$.faultGameMaxDuration");
faultGameClockExtension = stdJson.readUint(_json, "$.faultGameClockExtension");
faultGameMaxClockDuration = stdJson.readUint(_json, "$.faultGameMaxClockDuration");
faultGameGenesisBlock = stdJson.readUint(_json, "$.faultGameGenesisBlock");
faultGameGenesisOutputRoot = stdJson.readBytes32(_json, "$.faultGameGenesisOutputRoot");
faultGameWithdrawalDelay = stdJson.readUint(_json, "$.faultGameWithdrawalDelay");
......
......@@ -181,7 +181,8 @@ contract FPACOPS is Deploy, StdAssertions {
FaultDisputeGame gameImpl = FaultDisputeGame(payable(address(dgfProxy.gameImpls(GameTypes.CANNON))));
assertEq(gameImpl.maxGameDepth(), cfg.faultGameMaxDepth());
assertEq(gameImpl.splitDepth(), cfg.faultGameSplitDepth());
assertEq(gameImpl.gameDuration().raw(), cfg.faultGameMaxDuration());
assertEq(gameImpl.clockExtension().raw(), cfg.faultGameClockExtension());
assertEq(gameImpl.maxClockDuration().raw(), cfg.faultGameMaxClockDuration());
assertEq(gameImpl.absolutePrestate().raw(), bytes32(cfg.faultGameAbsolutePrestate()));
// Check the security override yoke configuration.
......@@ -191,7 +192,8 @@ contract FPACOPS is Deploy, StdAssertions {
assertEq(soyGameImpl.challenger(), cfg.l2OutputOracleChallenger());
assertEq(soyGameImpl.maxGameDepth(), cfg.faultGameMaxDepth());
assertEq(soyGameImpl.splitDepth(), cfg.faultGameSplitDepth());
assertEq(soyGameImpl.gameDuration().raw(), cfg.faultGameMaxDuration());
assertEq(soyGameImpl.clockExtension().raw(), cfg.faultGameClockExtension());
assertEq(soyGameImpl.maxClockDuration().raw(), cfg.faultGameMaxClockDuration());
assertEq(soyGameImpl.absolutePrestate().raw(), bytes32(cfg.faultGameAbsolutePrestate()));
// Check the AnchorStateRegistry configuration.
......@@ -211,13 +213,14 @@ contract FPACOPS is Deploy, StdAssertions {
console.log(" 1. Absolute Prestate: %x", cfg.faultGameAbsolutePrestate());
console.log(" 2. Max Depth: %d", cfg.faultGameMaxDepth());
console.log(" 3. Output / Execution split Depth: %d", cfg.faultGameSplitDepth());
console.log(" 4. Game Duration (seconds): %d", cfg.faultGameMaxDuration());
console.log(" 5. L2 Genesis block number: %d", cfg.faultGameGenesisBlock());
console.log(" 6. L2 Genesis output root: %x", uint256(cfg.faultGameGenesisOutputRoot()));
console.log(" 7. Proof Maturity Delay (seconds): ", cfg.proofMaturityDelaySeconds());
console.log(" 8. Dispute Game Finality Delay (seconds): ", cfg.disputeGameFinalityDelaySeconds());
console.log(" 9. Respected Game Type: ", cfg.respectedGameType());
console.log(" 10. Preimage Oracle Min Proposal Size (bytes): ", cfg.preimageOracleMinProposalSize());
console.log(" 11. Preimage Oracle Challenge Period (seconds): ", cfg.preimageOracleChallengePeriod());
console.log(" 4. Clock Extension (seconds): %d", cfg.faultGameClockExtension());
console.log(" 5. Max Clock Duration (seconds): %d", cfg.faultGameMaxClockDuration());
console.log(" 6. L2 Genesis block number: %d", cfg.faultGameGenesisBlock());
console.log(" 7. L2 Genesis output root: %x", uint256(cfg.faultGameGenesisOutputRoot()));
console.log(" 8. Proof Maturity Delay (seconds): ", cfg.proofMaturityDelaySeconds());
console.log(" 9. Dispute Game Finality Delay (seconds): ", cfg.disputeGameFinalityDelaySeconds());
console.log(" 10. Respected Game Type: ", cfg.respectedGameType());
console.log(" 11. Preimage Oracle Min Proposal Size (bytes): ", cfg.preimageOracleMinProposalSize());
console.log(" 12. Preimage Oracle Challenge Period (seconds): ", cfg.preimageOracleChallengePeriod());
}
}
......@@ -91,7 +91,8 @@ config=$(cat << EOL
"faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 44,
"faultGameMaxDuration": 1200,
"faultGameClockExtension": 0,
"faultGameMaxClockDuration": 600,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameSplitDepth": 14,
......
......@@ -116,8 +116,8 @@
"sourceCodeHash": "0xc4dbd17217b63f8117f56f78c213e57dda304fee7577fe296e1d804ebe049542"
},
"src/dispute/FaultDisputeGame.sol": {
"initCodeHash": "0x42c04ec50860cf8f1f772bc77dcbd20e7e06554a0962d16eaad2ee5fd748cfe0",
"sourceCodeHash": "0x8ea9b68ddfc6fce606065a789b7323d8b119aadb162c139f1878a9322b1e892b"
"initCodeHash": "0xf1a4519238b2e5ac5d1ca83c33135dc47e5c38029c82443975cfd00abf76a8bf",
"sourceCodeHash": "0x221732fb84ae56d7c526c2437820ec15cdd3ef293ae47f3383df8c943c737805"
},
"src/dispute/weth/DelayedWETH.sol": {
"initCodeHash": "0x7b6ec89eaec09e369426e73161a9c6932223bb1f974377190c3f6f552995da35",
......
......@@ -23,7 +23,12 @@
},
{
"internalType": "Duration",
"name": "_gameDuration",
"name": "_clockExtension",
"type": "uint64"
},
{
"internalType": "Duration",
"name": "_maxClockDuration",
"type": "uint64"
},
{
......@@ -187,6 +192,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "clockExtension",
"outputs": [
{
"internalType": "Duration",
"name": "clockExtension_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "createdAt",
......@@ -286,19 +304,6 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gameDuration",
"outputs": [
{
"internalType": "Duration",
"name": "gameDuration_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gameType",
......@@ -396,6 +401,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxClockDuration",
"outputs": [
{
"internalType": "Duration",
"name": "maxClockDuration_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxGameDepth",
......@@ -724,6 +742,11 @@
"name": "IncorrectBondAmount",
"type": "error"
},
{
"inputs": [],
"name": "InvalidClockExtension",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLocalIdent",
......@@ -744,6 +767,11 @@
"name": "InvalidSplitDepth",
"type": "error"
},
{
"inputs": [],
"name": "MaxDepthTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "NoCreditToClaim",
......
......@@ -23,7 +23,12 @@
},
{
"internalType": "Duration",
"name": "_gameDuration",
"name": "_clockExtension",
"type": "uint64"
},
{
"internalType": "Duration",
"name": "_maxClockDuration",
"type": "uint64"
},
{
......@@ -210,6 +215,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "clockExtension",
"outputs": [
{
"internalType": "Duration",
"name": "clockExtension_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "createdAt",
......@@ -309,19 +327,6 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gameDuration",
"outputs": [
{
"internalType": "Duration",
"name": "gameDuration_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gameType",
......@@ -419,6 +424,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxClockDuration",
"outputs": [
{
"internalType": "Duration",
"name": "maxClockDuration_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxGameDepth",
......@@ -765,6 +783,11 @@
"name": "IncorrectBondAmount",
"type": "error"
},
{
"inputs": [],
"name": "InvalidClockExtension",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLocalIdent",
......@@ -785,6 +808,11 @@
"name": "InvalidSplitDepth",
"type": "error"
},
{
"inputs": [],
"name": "MaxDepthTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "NoCreditToClaim",
......
......@@ -1988,7 +1988,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003b"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
}
],
"value": 0
......@@ -2014,7 +2014,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
}
],
"value": 0
......@@ -2475,7 +2475,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003b"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
}
],
"value": 0
......@@ -2553,7 +2553,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
}
],
"value": 0
......@@ -7246,7 +7246,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003a"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003b"
}
],
"value": 0
......@@ -36,8 +36,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
/// this depth, execution trace bisection begins.
uint256 internal immutable SPLIT_DEPTH;
/// @notice The duration of the game.
Duration internal immutable GAME_DURATION;
/// @notice The maximum duration that may accumulate on a team's chess clock before they may no longer respond.
Duration internal immutable MAX_CLOCK_DURATION;
/// @notice An onchain VM that performs single instruction steps on a fault proof program trace.
IBigStepper internal immutable VM;
......@@ -54,6 +54,10 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
/// @notice The chain ID of the L2 network this contract argues about.
uint256 internal immutable L2_CHAIN_ID;
/// @notice The duration of the clock extension. Will be doubled if the grandchild is the root claim of an execution
/// trace bisection subgame.
Duration internal immutable CLOCK_EXTENSION;
/// @notice The global root claim's position is always at gindex 1.
Position internal constant ROOT_POSITION = Position.wrap(1);
......@@ -88,14 +92,15 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
OutputRoot public startingOutputRoot;
/// @notice Semantic version.
/// @custom:semver 0.15.0
string public constant version = "0.15.0";
/// @custom:semver 0.16.1
string public constant version = "0.16.1";
/// @param _gameType The type ID of the game.
/// @param _absolutePrestate The absolute prestate of the instruction trace.
/// @param _maxGameDepth The maximum depth of bisection.
/// @param _splitDepth The final depth of the output bisection portion of the game.
/// @param _gameDuration The duration of the game.
/// @param _clockExtension The clock extension to perform when the remaining duration is less than the extension.
/// @param _maxClockDuration The maximum amount of time that may accumulate on a team's chess clock.
/// @param _vm An onchain VM that performs single instruction steps on an FPP trace.
/// @param _weth WETH contract for holding ETH.
/// @param _anchorStateRegistry The contract that stores the anchor state for each game type.
......@@ -105,20 +110,26 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
Claim _absolutePrestate,
uint256 _maxGameDepth,
uint256 _splitDepth,
Duration _gameDuration,
Duration _clockExtension,
Duration _maxClockDuration,
IBigStepper _vm,
IDelayedWETH _weth,
IAnchorStateRegistry _anchorStateRegistry,
uint256 _l2ChainId
) {
// The max game depth may not be greater than `LibPosition.MAX_POSITION_BITLEN - 1`.
if (_maxGameDepth > LibPosition.MAX_POSITION_BITLEN - 1) revert MaxDepthTooLarge();
// The split depth cannot be greater than or equal to the max game depth.
if (_splitDepth >= _maxGameDepth) revert InvalidSplitDepth();
// The clock extension may not be greater than the max clock duration.
if (_clockExtension.raw() > _maxClockDuration.raw()) revert InvalidClockExtension();
GAME_TYPE = _gameType;
ABSOLUTE_PRESTATE = _absolutePrestate;
MAX_GAME_DEPTH = _maxGameDepth;
SPLIT_DEPTH = _splitDepth;
GAME_DURATION = _gameDuration;
CLOCK_EXTENSION = _clockExtension;
MAX_CLOCK_DURATION = _maxClockDuration;
VM = _vm;
WETH = _weth;
ANCHOR_STATE_REGISTRY = _anchorStateRegistry;
......@@ -261,9 +272,22 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
// parent's clock timestamp.
Duration nextDuration = getChallengerDuration(_challengeIndex);
// INVARIANT: A move can never be made once its clock has exceeded `GAME_DURATION / 2`
// INVARIANT: A move can never be made once its clock has exceeded `MAX_CLOCK_DURATION`
// seconds of time.
if (nextDuration.raw() == GAME_DURATION.raw() >> 1) revert ClockTimeExceeded();
if (nextDuration.raw() == MAX_CLOCK_DURATION.raw()) revert ClockTimeExceeded();
// If the remaining clock time has less than `CLOCK_EXTENSION` seconds remaining, grant the potential
// grandchild's clock `CLOCK_EXTENSION` seconds. This is to ensure that, even if a player has to inherit another
// team's clock to counter a freeloader claim, they will always have enough time to to respond. This extension
// is bounded by the depth of the tree. If the potential grandchild is an execution trace bisection root, the
// clock extension is doubled. This is to allow for extra time for the off-chain challenge agent to generate
// the initial instruction trace on the native FPVM.
if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - CLOCK_EXTENSION.raw()) {
// If the potential grandchild is an execution trace bisection root, double the clock extension.
uint64 extensionPeriod =
nextPositionDepth == SPLIT_DEPTH - 1 ? CLOCK_EXTENSION.raw() * 2 : CLOCK_EXTENSION.raw();
nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - extensionPeriod);
}
// Construct the next clock with the new duration and the current block timestamp.
Clock nextClock = LibClock.wrap(nextDuration, Timestamp.wrap(uint64(block.timestamp)));
......@@ -387,9 +411,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
Duration challengeClockDuration = getChallengerDuration(_claimIndex);
// INVARIANT: Cannot resolve a subgame unless the clock of its would-be counter has expired
// INVARIANT: Assuming ordered subgame resolution, challengeClockDuration is always >= GAME_DURATION / 2 if all
// INVARIANT: Assuming ordered subgame resolution, challengeClockDuration is always >= MAX_CLOCK_DURATION if all
// descendant subgames are resolved
if (challengeClockDuration.raw() < GAME_DURATION.raw() >> 1) revert ClockNotExpired();
if (challengeClockDuration.raw() < MAX_CLOCK_DURATION.raw()) revert ClockNotExpired();
// INVARIANT: Cannot resolve a subgame twice.
if (resolvedSubgames[_claimIndex]) revert ClaimAlreadyResolved();
......@@ -634,7 +658,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
}
/// @notice Returns the amount of time elapsed on the potential challenger to `_claimIndex`'s chess clock. Maxes
/// out at `GAME_DURATION / 2`.
/// out at `MAX_CLOCK_DURATION`.
/// @param _claimIndex The index of the subgame root claim.
/// @return duration_ The time elapsed on the potential challenger to `_claimIndex`'s chess clock.
function getChallengerDuration(uint256 _claimIndex) public view returns (Duration duration_) {
......@@ -655,12 +679,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
// Compute the duration elapsed of the potential challenger's clock.
uint64 challengeDuration =
uint64(parentClock.duration().raw() + (block.timestamp - subgameRootClaim.clock.timestamp().raw()));
uint64 maxClockTime = GAME_DURATION.raw() >> 1;
if (challengeDuration > maxClockTime) {
duration_ = Duration.wrap(maxClockTime);
} else {
duration_ = Duration.wrap(challengeDuration);
}
duration_ = challengeDuration > MAX_CLOCK_DURATION.raw() ? MAX_CLOCK_DURATION : Duration.wrap(challengeDuration);
}
////////////////////////////////////////////////////////////////
......@@ -682,9 +701,14 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
splitDepth_ = SPLIT_DEPTH;
}
/// @notice Returns the game duration.
function gameDuration() external view returns (Duration gameDuration_) {
gameDuration_ = GAME_DURATION;
/// @notice Returns the max clock duration.
function maxClockDuration() external view returns (Duration maxClockDuration_) {
maxClockDuration_ = MAX_CLOCK_DURATION;
}
/// @notice Returns the clock extension constant.
function clockExtension() external view returns (Duration clockExtension_) {
clockExtension_ = CLOCK_EXTENSION;
}
/// @notice Returns the address of the VM.
......@@ -879,10 +903,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
{
// A position of 0 indicates that the starting claim is the absolute prestate. In this special case,
// we do not include the starting claim within the local context hash.
if (_startingPos.raw() == 0) {
uuid_ = Hash.wrap(keccak256(abi.encode(_disputed, _disputedPos)));
} else {
uuid_ = Hash.wrap(keccak256(abi.encode(_starting, _startingPos, _disputed, _disputedPos)));
}
uuid_ = _startingPos.raw() == 0
? Hash.wrap(keccak256(abi.encode(_disputed, _disputedPos)))
: Hash.wrap(keccak256(abi.encode(_starting, _startingPos, _disputed, _disputedPos)));
}
}
......@@ -34,7 +34,8 @@ contract PermissionedDisputeGame is FaultDisputeGame {
/// @param _absolutePrestate The absolute prestate of the instruction trace.
/// @param _maxGameDepth The maximum depth of bisection.
/// @param _splitDepth The final depth of the output bisection portion of the game.
/// @param _gameDuration The duration of the game.
/// @param _clockExtension The clock extension to perform when the remaining duration is less than the extension.
/// @param _maxClockDuration The maximum amount of time that may accumulate on a team's chess clock.
/// @param _vm An onchain VM that performs single instruction steps on an FPP trace.
/// @param _weth WETH contract for holding ETH.
/// @param _anchorStateRegistry The contract that stores the anchor state for each game type.
......@@ -46,7 +47,8 @@ contract PermissionedDisputeGame is FaultDisputeGame {
Claim _absolutePrestate,
uint256 _maxGameDepth,
uint256 _splitDepth,
Duration _gameDuration,
Duration _clockExtension,
Duration _maxClockDuration,
IBigStepper _vm,
IDelayedWETH _weth,
IAnchorStateRegistry _anchorStateRegistry,
......@@ -59,7 +61,8 @@ contract PermissionedDisputeGame is FaultDisputeGame {
_absolutePrestate,
_maxGameDepth,
_splitDepth,
_gameDuration,
_clockExtension,
_maxClockDuration,
_vm,
_weth,
_anchorStateRegistry,
......
......@@ -7,6 +7,10 @@ import "src/libraries/DisputeErrors.sol";
/// @title LibPosition
/// @notice This library contains helper functions for working with the `Position` type.
library LibPosition {
/// @notice the `MAX_POSITION_BITLEN` is the number of bits that the `Position` type, and the implementation of
/// its behavior within this library, can safely support.
uint8 internal constant MAX_POSITION_BITLEN = 126;
/// @notice Computes a generalized index (2^{depth} + indexAtDepth).
/// @param _depth The depth of the position.
/// @param _indexAtDepth The index at the depth of the position.
......
......@@ -91,6 +91,12 @@ error ClaimAboveSplit();
/// depth of the game.
error InvalidSplitDepth();
/// @notice Thrown on deployment if the max clock duration is less than or equal to the clock extension.
error InvalidClockExtension();
/// @notice Thrown on deployment if the max depth is greater than `LibPosition.`
error MaxDepthTooLarge();
/// @notice Thrown when trying to step against a claim for a second time, after it has already been countered with
/// an instruction step.
error DuplicateStep();
......
......@@ -343,7 +343,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest {
_proposedGameIndex = disputeGameFactory.gameCount() - 1;
// Warp beyond the chess clocks and finalize the game.
vm.warp(block.timestamp + game.gameDuration().raw() / 2 + 1 seconds);
vm.warp(block.timestamp + game.maxClockDuration().raw() + 1 seconds);
// Fund the portal so that we can withdraw ETH.
vm.deal(address(optimismPortal2), 0xFFFFFFFF);
......
......@@ -57,7 +57,8 @@ contract PermissionedDisputeGame_Init is DisputeGameFactory_Init {
_absolutePrestate: absolutePrestate,
_maxGameDepth: 2 ** 3,
_splitDepth: 2 ** 2,
_gameDuration: Duration.wrap(7 days),
_clockExtension: Duration.wrap(3 hours),
_maxClockDuration: Duration.wrap(3.5 days),
_vm: _vm,
_weth: _weth,
_anchorStateRegistry: anchorStateRegistry,
......@@ -79,7 +80,7 @@ contract PermissionedDisputeGame_Init is DisputeGameFactory_Init {
assertEq(gameProxy.absolutePrestate().raw(), absolutePrestate.raw());
assertEq(gameProxy.maxGameDepth(), 2 ** 3);
assertEq(gameProxy.splitDepth(), 2 ** 2);
assertEq(gameProxy.gameDuration().raw(), 7 days);
assertEq(gameProxy.maxClockDuration().raw(), 3.5 days);
assertEq(address(gameProxy.vm()), address(_vm));
// Label the proxy
......
......@@ -127,7 +127,7 @@ contract OptimismPortal2_Invariant_Harness is CommonTest {
_proposedGameIndex = disputeGameFactory.gameCount() - 1;
// Warp beyond the finalization period for the dispute game and resolve it.
vm.warp(block.timestamp + game.gameDuration().raw() + 1 seconds);
vm.warp(block.timestamp + (game.maxClockDuration().raw() * 2) + 1 seconds);
game.resolveClaim(0);
game.resolve();
......
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