Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
2cbbf069
Commit
2cbbf069
authored
Jun 29, 2023
by
clabby
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Time-box `FaultDisputeGame::resolve()`
parent
08d44aea
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
101 additions
and
9 deletions
+101
-9
FaultDisputeGame.sol
.../contracts-bedrock/contracts/dispute/FaultDisputeGame.sol
+21
-7
DisputeErrors.sol
...s/contracts-bedrock/contracts/libraries/DisputeErrors.sol
+3
-0
FaultDisputeGame.t.sol
...s/contracts-bedrock/contracts/test/FaultDisputeGame.t.sol
+77
-2
No files found.
packages/contracts-bedrock/contracts/dispute/FaultDisputeGame.sol
View file @
2cbbf069
...
@@ -275,10 +275,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
...
@@ -275,10 +275,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
/// @inheritdoc IDisputeGame
/// @inheritdoc IDisputeGame
function resolve() external returns (GameStatus status_) {
function resolve() external returns (GameStatus status_) {
// TODO: Do not allow resolution before clocks run out.
// If the game is not in progress, it cannot be resolved.
if (status != GameStatus.IN_PROGRESS) {
if (status != GameStatus.IN_PROGRESS) {
// If the game is not in progress, it cannot be resolved.
revert GameNotInProgress();
revert GameNotInProgress();
}
}
...
@@ -298,7 +296,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
...
@@ -298,7 +296,6 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
// If the claim is not a dangling node above the bottom of the tree,
// If the claim is not a dangling node above the bottom of the tree,
// we can skip over it. These nodes are not relevant to the game resolution.
// we can skip over it. These nodes are not relevant to the game resolution.
Position claimPos = claim.position;
if (claim.countered) {
if (claim.countered) {
continue;
continue;
}
}
...
@@ -306,7 +303,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
...
@@ -306,7 +303,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
// If the claim is a dangling node, we can check if it is the left-most
// If the claim is a dangling node, we can check if it is the left-most
// dangling node we've come across so far. If it is, we can update the
// dangling node we've come across so far. If it is, we can update the
// left-most trace index.
// left-most trace index.
uint256 traceIndex = claim
Pos
.traceIndex(MAX_GAME_DEPTH);
uint256 traceIndex = claim
.position
.traceIndex(MAX_GAME_DEPTH);
if (traceIndex < leftMostTraceIndex) {
if (traceIndex < leftMostTraceIndex) {
leftMostTraceIndex = traceIndex;
leftMostTraceIndex = traceIndex;
unchecked {
unchecked {
...
@@ -315,12 +312,29 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
...
@@ -315,12 +312,29 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, Semver {
}
}
}
}
// Create a reference to the left most uncontested claim and its parent.
ClaimData storage leftMostUncontested = claimData[leftMostIndex];
// If the left most uncontested claim's parent has not expired their clock, the game
// cannot be resolved. If the left most uncontested claim is the root, no nodes qualified,
// and we check if 3.5 days has passed since the root claim's creation.
uint256 parentIndex = leftMostUncontested.parentIndex;
Clock opposingClock = parentIndex == type(uint32).max
? leftMostUncontested.clock
: claimData[parentIndex].clock;
if (
Duration.unwrap(opposingClock.duration()) +
(block.timestamp - Timestamp.unwrap(opposingClock.timestamp())) <=
Duration.unwrap(GAME_DURATION) >> 1
) {
revert ClockNotExpired();
}
// If the left-most dangling node is at an even depth, the defender wins.
// If the left-most dangling node is at an even depth, the defender wins.
// Otherwise, the challenger wins and the root claim is deemed invalid.
// Otherwise, the challenger wins and the root claim is deemed invalid.
if (
if (
// slither-disable-next-line weak-prng
// slither-disable-next-line weak-prng
claimData[leftMostIndex].position.depth() % 2 == 0 &&
leftMostUncontested.position.depth() % 2 == 0 && leftMostTraceIndex != type(uint128).max
leftMostTraceIndex != type(uint128).max
) {
) {
status_ = GameStatus.DEFENDER_WINS;
status_ = GameStatus.DEFENDER_WINS;
} else {
} else {
...
...
packages/contracts-bedrock/contracts/libraries/DisputeErrors.sol
View file @
2cbbf069
...
@@ -39,6 +39,9 @@ error GameNotInProgress();
...
@@ -39,6 +39,9 @@ error GameNotInProgress();
/// @notice Thrown when a move is attempted to be made after the clock has timed out.
/// @notice Thrown when a move is attempted to be made after the clock has timed out.
error ClockTimeExceeded();
error ClockTimeExceeded();
/// @notice Thrown when the game is attempted to be resolved too early.
error ClockNotExpired();
/// @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
/// @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
error GameDepthExceeded();
error GameDepthExceeded();
...
...
packages/contracts-bedrock/contracts/test/FaultDisputeGame.t.sol
View file @
2cbbf069
...
@@ -130,7 +130,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
...
@@ -130,7 +130,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
}
}
/// @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error.
/// @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error.
function test_
defendRoot_invalidMove
_reverts() public {
function test_
move_defendRoot
_reverts() public {
vm.expectRevert(CannotDefendRootClaim.selector);
vm.expectRevert(CannotDefendRootClaim.selector);
gameProxy.defend(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.defend(0, Claim.wrap(bytes32(uint256(5))));
}
}
...
@@ -175,6 +175,49 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
...
@@ -175,6 +175,49 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
}
}
/// @notice Static unit test for the correctness of the chess clock incrementation.
function test_move_clockCorrectness_succeeds() public {
(, , , , Clock clock) = gameProxy.claimData(0);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))))
);
Claim claim = Claim.wrap(bytes32(uint256(5)));
vm.warp(block.timestamp + 15);
gameProxy.attack(0, claim);
(, , , , clock) = gameProxy.claimData(1);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(15), Timestamp.wrap(uint64(block.timestamp))))
);
vm.warp(block.timestamp + 10);
gameProxy.attack(1, claim);
(, , , , clock) = gameProxy.claimData(2);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(10), Timestamp.wrap(uint64(block.timestamp))))
);
vm.warp(block.timestamp + 10);
gameProxy.attack(2, claim);
(, , , , clock) = gameProxy.claimData(3);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(25), Timestamp.wrap(uint64(block.timestamp))))
);
vm.warp(block.timestamp + 10);
gameProxy.attack(3, claim);
(, , , , clock) = gameProxy.claimData(4);
assertEq(
Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(20), Timestamp.wrap(uint64(block.timestamp))))
);
}
/// @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should
/// @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should
/// revert with the `ClaimAlreadyExists` error.
/// revert with the `ClaimAlreadyExists` error.
function test_move_duplicateClaim_reverts() public {
function test_move_duplicateClaim_reverts() public {
...
@@ -189,7 +232,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
...
@@ -189,7 +232,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
}
}
/// @dev Static unit test for the correctness of an opening attack.
/// @dev Static unit test for the correctness of an opening attack.
function test_simpleAttack_succeeds() public {
function test_
move_
simpleAttack_succeeds() public {
// Warp ahead 5 seconds.
// Warp ahead 5 seconds.
vm.warp(block.timestamp + 5);
vm.warp(block.timestamp + 5);
...
@@ -237,11 +280,19 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
...
@@ -237,11 +280,19 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/// @dev Static unit test for the correctness an uncontested root resolution.
/// @dev Static unit test for the correctness an uncontested root resolution.
function test_resolve_rootUncontested_succeeds() public {
function test_resolve_rootUncontested_succeeds() public {
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve();
GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
}
}
/// @dev Static unit test for the correctness an uncontested root resolution.
function test_resolve_rootUncontestedClockNotExpired_succeeds() public {
vm.warp(block.timestamp + 3 days + 12 hours);
vm.expectRevert(ClockNotExpired.selector);
gameProxy.resolve();
}
/// @dev Static unit test asserting that resolve reverts when the game state is
/// @dev Static unit test asserting that resolve reverts when the game state is
/// not in progress.
/// not in progress.
function test_resolve_notInProgress_reverts() public {
function test_resolve_notInProgress_reverts() public {
...
@@ -263,6 +314,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
...
@@ -263,6 +314,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
function test_resolve_rootContested_succeeds() public {
function test_resolve_rootContested_succeeds() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve();
GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
...
@@ -273,6 +326,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
...
@@ -273,6 +326,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve();
GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
...
@@ -285,6 +340,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
...
@@ -285,6 +340,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(7))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(7))));
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
GameStatus status = gameProxy.resolve();
GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(status), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
...
@@ -499,6 +556,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
...
@@ -499,6 +556,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
// Play the game until a step is forced.
// Play the game until a step is forced.
challenger.play(0);
challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the honest player challenged the root
// Resolve the game and assert that the honest player challenged the root
// claim successfully.
// claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
...
@@ -517,6 +577,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
...
@@ -517,6 +577,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
// Play the game until a step is forced.
// Play the game until a step is forced.
challenger.play(0);
challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the dishonest player challenged the root
// Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully.
// claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
...
@@ -535,6 +598,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena {
...
@@ -535,6 +598,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena {
// Play the game until a step is forced.
// Play the game until a step is forced.
challenger.play(0);
challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the honest player challenged the root
// Resolve the game and assert that the honest player challenged the root
// claim successfully.
// claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
...
@@ -553,6 +619,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena {
...
@@ -553,6 +619,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena {
// Play the game until a step is forced.
// Play the game until a step is forced.
challenger.play(0);
challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the dishonest player challenged the root
// Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully.
// claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
...
@@ -571,6 +640,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena {
...
@@ -571,6 +640,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena {
// Play the game until a step is forced.
// Play the game until a step is forced.
challenger.play(0);
challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the honest player challenged the root
// Resolve the game and assert that the honest player challenged the root
// claim successfully.
// claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
...
@@ -589,6 +661,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot3 is OneVsOne_Arena {
...
@@ -589,6 +661,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot3 is OneVsOne_Arena {
// Play the game until a step is forced.
// Play the game until a step is forced.
challenger.play(0);
challenger.play(0);
// Warp ahead to expire the other player's clock.
vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds);
// Resolve the game and assert that the dishonest player challenged the root
// Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully.
// claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment