Skip to content

Commit d1a1379

Browse files
expanded test coverage
1 parent c5b8c57 commit d1a1379

File tree

3 files changed

+327
-1
lines changed

3 files changed

+327
-1
lines changed

op-e2e/faultproofs/output_alphabet_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ func TestOutputAlphabetGame_ReclaimBond(t *testing.T) {
129129

130130
// Advance the time past the finalization delay
131131
// Finalization delay is the same as the credit unlock delay
132-
sys.TimeTravelClock.AdvanceTime(game.CreditUnlockDuration(ctx))
132+
// But just warp way into the future to be safe
133+
sys.TimeTravelClock.AdvanceTime(game.CreditUnlockDuration(ctx) * 2)
133134
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
134135

135136
// Wait for alice to have no available credit

packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_Init {
257257
}
258258

259259
/// @notice Tests that isGameProper will return false if the game is not the respected game type.
260+
/// @param _gameType The game type to use for the test.
260261
function testFuzz_isGameProper_isNotRespected_succeeds(GameType _gameType) public {
261262
if (_gameType.raw() == gameProxy.gameType().raw()) {
262263
_gameType = GameType.wrap(_gameType.raw() + 1);
@@ -283,6 +284,7 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_Init {
283284
}
284285

285286
/// @notice Tests that isGameProper will return false if the game is retired.
287+
/// @param _retirementTimestamp The retirement timestamp to use for the test.
286288
function testFuzz_isGameProper_isRetired_succeeds(uint64 _retirementTimestamp) public {
287289
// Make sure retirement timestamp is later than the game's creation time.
288290
_retirementTimestamp = uint64(bound(_retirementTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max));
@@ -298,6 +300,206 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_Init {
298300
}
299301
}
300302

303+
contract AnchorStateRegistry_IsGameResolved_Test is AnchorStateRegistry_Init {
304+
/// @notice Tests that isGameResolved will return true if the game is resolved.
305+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
306+
function testFuzz_isGameResolved_challengerWins_succeeds(uint256 _resolvedAtTimestamp) public {
307+
// Bound resolvedAt to be less than or equal to current timestamp.
308+
_resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp);
309+
310+
// Mock the resolvedAt timestamp.
311+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp));
312+
313+
// Mock the status to be CHALLENGER_WINS.
314+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.CHALLENGER_WINS));
315+
316+
// Game should be resolved.
317+
assertTrue(anchorStateRegistry.isGameResolved(gameProxy));
318+
}
319+
320+
/// @notice Tests that isGameResolved will return true if the game is resolved.
321+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
322+
function testFuzz_isGameResolved_defenderWins_succeeds(uint256 _resolvedAtTimestamp) public {
323+
// Bound resolvedAt to be less than or equal to current timestamp.
324+
_resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp);
325+
326+
// Mock the resolvedAt timestamp.
327+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp));
328+
329+
// Mock the status to be DEFENDER_WINS.
330+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
331+
332+
// Game should be resolved.
333+
assertTrue(anchorStateRegistry.isGameResolved(gameProxy));
334+
}
335+
336+
/// @notice Tests that isGameResolved will return false if the game is in progress and not resolved.
337+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
338+
function testFuzz_isGameResolved_inProgressNotResolved_succeeds(uint256 _resolvedAtTimestamp) public {
339+
// Bound resolvedAt to be less than or equal to current timestamp.
340+
_resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp);
341+
342+
// Mock the resolvedAt timestamp.
343+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp));
344+
345+
// Mock the status to be IN_PROGRESS.
346+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS));
347+
348+
// Game should not be resolved.
349+
assertFalse(anchorStateRegistry.isGameResolved(gameProxy));
350+
}
351+
}
352+
353+
contract AnchorStateRegistry_IsGameAirgapped_TestFail is AnchorStateRegistry_Init {
354+
/// @notice Tests that isGameAirgapped will return true if the game is airgapped.
355+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
356+
function testFuzz_isGameAirgapped_isAirgapped_succeeds(uint256 _resolvedAtTimestamp) public {
357+
// Warp forward by disputeGameFinalityDelaySeconds.
358+
vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds());
359+
360+
// Bound resolvedAt to be at least disputeGameFinalityDelaySeconds in the past.
361+
_resolvedAtTimestamp =
362+
bound(_resolvedAtTimestamp, 0, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds() - 1);
363+
364+
// Mock the resolvedAt timestamp.
365+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp));
366+
367+
// Game should be airgapped.
368+
assertTrue(anchorStateRegistry.isGameAirgapped(gameProxy));
369+
}
370+
371+
/// @notice Tests that isGameAirgapped will return false if the game is not airgapped.
372+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
373+
function testFuzz_isGameAirgapped_isNotAirgapped_succeeds(uint256 _resolvedAtTimestamp) public {
374+
// Warp forward by disputeGameFinalityDelaySeconds.
375+
vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds());
376+
377+
// Bound resolvedAt to be less than disputeGameFinalityDelaySeconds in the past.
378+
_resolvedAtTimestamp = bound(
379+
_resolvedAtTimestamp, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds(), block.timestamp
380+
);
381+
382+
// Mock the resolvedAt timestamp.
383+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp));
384+
385+
// Game should not be airgapped.
386+
assertFalse(anchorStateRegistry.isGameAirgapped(gameProxy));
387+
}
388+
}
389+
390+
contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_Init {
391+
/// @notice Tests that isGameClaimValid will return true if the game claim is valid.
392+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
393+
function testFuzz_isGameClaimValid_claimIsValid_succeeds(uint256 _resolvedAtTimestamp) public {
394+
// Warp forward by disputeGameFinalityDelaySeconds.
395+
vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds());
396+
397+
// Bound resolvedAt to be at least disputeGameFinalityDelaySeconds in the past.
398+
_resolvedAtTimestamp =
399+
bound(_resolvedAtTimestamp, 1, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds() - 1);
400+
401+
// Mock that the game was respected.
402+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true));
403+
404+
// Mock the resolvedAt timestamp.
405+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp));
406+
407+
// Mock the status to be DEFENDER_WINS.
408+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
409+
410+
// Claim should be valid.
411+
assertTrue(anchorStateRegistry.isGameClaimValid(gameProxy));
412+
}
413+
414+
/// @notice Tests that isGameClaimValid will return false if the game is not registered.
415+
function testFuzz_isGameClaimValid_notRegistered_succeeds() public {
416+
// Mock the DisputeGameFactory to make it seem that the game was not registered.
417+
vm.mockCall(
418+
address(disputeGameFactory),
419+
abi.encodeCall(
420+
disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData())
421+
),
422+
abi.encode(address(0), 0)
423+
);
424+
425+
// Claim should not be valid.
426+
assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy));
427+
}
428+
429+
/// @notice Tests that isGameClaimValid will return false if the game is not respected.
430+
/// @param _gameType The game type to use for the test.
431+
function testFuzz_isGameClaimValid_isNotRespected_succeeds(GameType _gameType) public {
432+
if (_gameType.raw() == gameProxy.gameType().raw()) {
433+
_gameType = GameType.wrap(_gameType.raw() + 1);
434+
}
435+
436+
// Mock that the game was not respected.
437+
vm.mockCall(
438+
address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(false)
439+
);
440+
441+
// Claim should not be valid.
442+
assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy));
443+
}
444+
445+
/// @notice Tests that isGameClaimValid will return false if the game is blacklisted.
446+
function testFuzz_isGameClaimValid_isBlacklisted_succeeds() public {
447+
// Mock the disputeGameBlacklist call to return true.
448+
vm.mockCall(
449+
address(optimismPortal2),
450+
abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)),
451+
abi.encode(true)
452+
);
453+
454+
// Claim should not be valid.
455+
assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy));
456+
}
457+
458+
/// @notice Tests that isGameClaimValid will return false if the game is retired.
459+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
460+
function testFuzz_isGameClaimValid_isRetired_succeeds(uint256 _resolvedAtTimestamp) public {
461+
// Make sure retirement timestamp is later than the game's creation time.
462+
_resolvedAtTimestamp = uint64(bound(_resolvedAtTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max));
463+
464+
// Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time.
465+
vm.mockCall(
466+
address(optimismPortal2),
467+
abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()),
468+
abi.encode(_resolvedAtTimestamp)
469+
);
470+
471+
// Claim should not be valid.
472+
assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy));
473+
}
474+
475+
/// @notice Tests that isGameClaimValid will return false if the game is not resolved.
476+
function testFuzz_isGameClaimValid_notResolved_succeeds() public {
477+
// Mock the status to be IN_PROGRESS.
478+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS));
479+
480+
// Claim should not be valid.
481+
assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy));
482+
}
483+
484+
/// @notice Tests that isGameClaimValid will return false if the game is not airgapped.
485+
/// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test.
486+
function testFuzz_isGameClaimValid_notAirgapped_succeeds(uint256 _resolvedAtTimestamp) public {
487+
// Warp forward by disputeGameFinalityDelaySeconds.
488+
vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds());
489+
490+
// Bound resolvedAt to be less than disputeGameFinalityDelaySeconds in the past.
491+
_resolvedAtTimestamp = bound(
492+
_resolvedAtTimestamp, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds(), block.timestamp
493+
);
494+
495+
// Mock the resolvedAt timestamp.
496+
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp));
497+
498+
// Claim should not be valid.
499+
assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy));
500+
}
501+
}
502+
301503
contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_Init {
302504
/// @notice Tests that setAnchorState will succeed if the game is valid, the game block
303505
/// number is greater than the current anchor root block number, and the game is the

packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,44 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
351351
});
352352
}
353353

354+
/// @dev Tests that the constructor of the `FaultDisputeGame` reverts when the `_gameType`
355+
/// parameter is set to the reserved `type(uint32).max` game type.
356+
function test_constructor_reservedGameType_reverts() public {
357+
AlphabetVM alphabetVM = new AlphabetVM(
358+
absolutePrestate,
359+
IPreimageOracle(
360+
DeployUtils.create1({
361+
_name: "PreimageOracle",
362+
_args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0)))
363+
})
364+
)
365+
);
366+
367+
vm.expectRevert(ReservedGameType.selector);
368+
DeployUtils.create1({
369+
_name: "FaultDisputeGame",
370+
_args: DeployUtils.encodeConstructor(
371+
abi.encodeCall(
372+
IFaultDisputeGame.__constructor__,
373+
(
374+
IFaultDisputeGame.GameConstructorParams({
375+
gameType: GameType.wrap(type(uint32).max),
376+
absolutePrestate: absolutePrestate,
377+
maxGameDepth: 16,
378+
splitDepth: 8,
379+
clockExtension: Duration.wrap(3 hours),
380+
maxClockDuration: Duration.wrap(3.5 days),
381+
vm: alphabetVM,
382+
weth: IDelayedWETH(payable(address(0))),
383+
anchorStateRegistry: IAnchorStateRegistry(address(0)),
384+
l2ChainId: 10
385+
})
386+
)
387+
)
388+
)
389+
});
390+
}
391+
354392
/// @dev Tests that the game's root claim is set correctly.
355393
function test_rootClaim_succeeds() public view {
356394
assertEq(gameProxy.rootClaim().raw(), ROOT_CLAIM.raw());
@@ -460,6 +498,20 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
460498
assertEq(gameProxy.l1Head().raw(), blockhash(block.number - 1));
461499
}
462500

501+
/// @dev Tests that the game cannot be initialized when the anchor root is not found.
502+
function test_initialize_anchorRootNotFound_reverts() public {
503+
// Mock the AnchorStateRegistry to return a zero root.
504+
vm.mockCall(
505+
address(anchorStateRegistry),
506+
abi.encodeCall(IAnchorStateRegistry.getAnchorRoot, ()),
507+
abi.encode(Hash.wrap(bytes32(0)), 0)
508+
);
509+
510+
// Creation should fail.
511+
vm.expectRevert(AnchorRootNotFound.selector);
512+
gameProxy = IFaultDisputeGame(payable(address(disputeGameFactory.create(GAME_TYPE, _dummyClaim(), hex""))));
513+
}
514+
463515
/// @dev Tests that the game cannot be initialized twice.
464516
function test_initialize_onlyOnce_succeeds() public {
465517
vm.expectRevert(AlreadyInitialized.selector);
@@ -1819,6 +1871,77 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
18191871
assertEq(updatedRoot.raw(), root.raw());
18201872
}
18211873

1874+
function test_claimCredit_refundMode_succeeds() public {
1875+
// Set up actors.
1876+
address alice = address(0xa11ce);
1877+
address bob = address(0xb0b);
1878+
1879+
// Give the game proxy 1 extra ether, unregistered.
1880+
vm.deal(address(gameProxy), 1 ether);
1881+
1882+
// Perform a bonded move.
1883+
Claim claim = _dummyClaim();
1884+
1885+
// Bond the first claim.
1886+
uint256 firstBond = _getRequiredBond(0);
1887+
vm.deal(alice, firstBond);
1888+
(,,,, Claim disputed,,) = gameProxy.claimData(0);
1889+
vm.prank(alice);
1890+
gameProxy.attack{ value: firstBond }(disputed, 0, claim);
1891+
1892+
// Bond the second claim.
1893+
uint256 secondBond = _getRequiredBond(1);
1894+
vm.deal(bob, secondBond);
1895+
(,,,, disputed,,) = gameProxy.claimData(1);
1896+
vm.prank(bob);
1897+
gameProxy.attack{ value: secondBond }(disputed, 1, claim);
1898+
1899+
// Warp past the finalization period
1900+
vm.warp(block.timestamp + 3 days + 12 hours);
1901+
1902+
// Resolve the game.
1903+
// Second claim wins, so bob should get alice's credit.
1904+
gameProxy.resolveClaim(2, 0);
1905+
gameProxy.resolveClaim(1, 0);
1906+
gameProxy.resolveClaim(0, 0);
1907+
gameProxy.resolve();
1908+
1909+
// Wait for finalization delay.
1910+
vm.warp(block.timestamp + 3.5 days + 1 seconds);
1911+
1912+
// Mock that the game proxy is not proper, trigger refund mode.
1913+
vm.mockCall(
1914+
address(anchorStateRegistry),
1915+
abi.encodeCall(anchorStateRegistry.isGameProper, (gameProxy)),
1916+
abi.encode(false)
1917+
);
1918+
1919+
// Close the game.
1920+
gameProxy.closeGame();
1921+
1922+
// Assert bond distribution mode is refund mode.
1923+
assertTrue(gameProxy.bondDistributionMode() == BondDistributionMode.REFUND);
1924+
1925+
// Claim credit once to trigger unlock period.
1926+
gameProxy.claimCredit(alice);
1927+
gameProxy.claimCredit(bob);
1928+
1929+
// Wait for the withdrawal delay.
1930+
vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds);
1931+
1932+
// Grab balances before claim.
1933+
uint256 aliceBalanceBefore = alice.balance;
1934+
uint256 bobBalanceBefore = bob.balance;
1935+
1936+
// Claim credit again to get the bond back.
1937+
gameProxy.claimCredit(alice);
1938+
gameProxy.claimCredit(bob);
1939+
1940+
// Should have original balance again.
1941+
assertEq(alice.balance, aliceBalanceBefore + firstBond);
1942+
assertEq(bob.balance, bobBalanceBefore + secondBond);
1943+
}
1944+
18221945
/// @dev Static unit test asserting that credit may not be drained past allowance through reentrancy.
18231946
function test_claimCredit_claimAlreadyResolved_reverts() public {
18241947
ClaimCreditReenter reenter = new ClaimCreditReenter(gameProxy, vm);

0 commit comments

Comments
 (0)