Commit 507ddd2c authored by clabby's avatar clabby

Start simple bisection

parent 93a02de0
...@@ -11,7 +11,7 @@ import { Clone } from "../libraries/Clone.sol"; ...@@ -11,7 +11,7 @@ import { Clone } from "../libraries/Clone.sol";
import { LibHashing } from "./lib/LibHashing.sol"; import { LibHashing } from "./lib/LibHashing.sol";
import { LibPosition } from "./lib/LibPosition.sol"; import { LibPosition } from "./lib/LibPosition.sol";
import { LibClock } from "./lib/LibClock.sol"; import { LibClock } from "./lib/LibClock.sol";
import "../libraries/DisputeTypes.sol"; import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol"; import "../libraries/DisputeErrors.sol";
...@@ -33,13 +33,14 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -33,13 +33,14 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
/** /**
* @notice The max depth of the game. * @notice The max depth of the game.
* @dev TODO: Update this to the value that we will use in prod. * @dev TODO: Update this to the value that we will use in prod. Do we want to have the factory
* set this value? Should it be a constant?
*/ */
uint256 internal constant MAX_GAME_DEPTH = 4; uint256 internal constant MAX_GAME_DEPTH = 4;
/** /**
* @notice The duration of the game. * @notice The duration of the game.
* @dev TODO: Account for resolution buffer. * @dev TODO: Account for resolution buffer. (?)
*/ */
Duration internal constant GAME_DURATION = Duration.wrap(7 days); Duration internal constant GAME_DURATION = Duration.wrap(7 days);
...@@ -67,55 +68,31 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -67,55 +68,31 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
* @notice The left most, deepest position found during the resolution phase. * @notice The left most, deepest position found during the resolution phase.
* @dev Defaults to the position of the root claim, but will be set during the resolution * @dev Defaults to the position of the root claim, but will be set during the resolution
* phase to the left most, deepest position found (if any qualify.) * phase to the left most, deepest position found (if any qualify.)
* @dev TODO: Consider removing this if games can be resolved within a single block reliably.
*/ */
Position public leftMostPosition; Position public leftMostPosition;
/** /**
* @notice Maps a unique ClaimHash to a Claim. * @notice An append-only array of all claims made during the dispute game.
*/
mapping(ClaimHash => Claim) public claims;
/**
* @notice Maps a unique ClaimHash to its parent.
*/
mapping(ClaimHash => ClaimHash) public parents;
/**
* @notice Maps a unique ClaimHash to its position in the game tree.
*/ */
mapping(ClaimHash => Position) public positions; ClaimData[] public claimData;
/**
* @notice Maps a unique ClaimHash to a Bond.
*/
mapping(ClaimHash => BondAmount) public bonds;
/**
* @notice Maps a unique ClaimHash its chess clock.
*/
mapping(ClaimHash => Clock) public clocks;
/**
* @notice Maps a unique ClaimHash to its reference counter.
*/
mapping(ClaimHash => uint64) public rc;
/**
* @notice Tracks whether or not a unique ClaimHash has been countered.
*/
mapping(ClaimHash => bool) public countered;
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// External Logic // // External Logic //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /**
* Attack a disagreed upon ClaimHash. * @inheritdoc IFaultDisputeGame
* @param disagreement Disagreed upon ClaimHash
* @param pivot The supplied pivot to the disagreement.
*/ */
function attack(ClaimHash disagreement, Claim pivot) external { function attack(uint256 parentIndex, Claim pivot) external payable {
_move(disagreement, pivot, true); _move(parentIndex, pivot, true);
} }
// TODO: Go right instead of left /**
// The pivot goes into the right subtree rather than the left subtree * @inheritdoc IFaultDisputeGame
function defend(ClaimHash agreement, Claim pivot) external { */
_move(agreement, pivot, false); function defend(uint256 parentIndex, Claim pivot) external payable {
_move(parentIndex, pivot, false);
} }
/** /**
...@@ -135,100 +112,82 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -135,100 +112,82 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
/** /**
* @notice Internal move function, used by both `attack` and `defend`. * @notice Internal move function, used by both `attack` and `defend`.
* @param claimHash The claim hash that the move is being made against. * @param challengeIndex The index of the claim being moved against.
* @param pivot The pivot point claim provided in response to `claimHash`. * @param pivot The claim at the next logical position in the game.
* @param isAttack Whether or not the move is an attack or defense. * @param isAttack Whether or not the move is an attack or defense.
*/ */
function _move(ClaimHash claimHash, Claim pivot, bool isAttack) internal { function _move(
// TODO: Require & store bond for the pivot point claim uint256 challengeIndex,
Claim pivot,
// Get the position of the claimHash bool isAttack
Position claimHashPos = positions[claimHash]; ) internal {
// Moves cannot be made unless the game is currently in progress.
// If the current depth of the claimHash is 0, revert. The root claim cannot be defended, only if (status != GameStatus.IN_PROGRESS) {
// attacked. revert GameNotInProgress();
if (LibPosition.depth(claimHashPos) == 0 && !isAttack) {
revert CannotDefendRootClaim();
} }
// If the `claimHash` is at max depth - 1, we can perform a step. // The only move that can be made against a root claim is an attack. This is because the
if (LibPosition.depth(claimHashPos) == MAX_GAME_DEPTH - 1) { // root claim commits to the entire state; Therefore, the only valid defense is to do
// TODO: Step // nothing if it is agreed with.
revert("todo: unimplemented"); if (challengeIndex == 0 && !isAttack) {
revert CannotDefendRootClaim();
} }
// Get the position of the move. // Get the parent
Position pivotPos = isAttack ? LibPosition.attack(claimHashPos) : LibPosition.defend(claimHashPos); ClaimData memory parent = claimData[challengeIndex];
// Compute the claim hash for the pivot point claim // The parent must exist.
ClaimHash pivotClaimHash = LibHashing.hashClaimPos(pivot, pivotPos); if (Claim.unwrap(parent.claim) == bytes32(0)) {
revert ParentDoesNotExist();
// Revert if the same claim has already been made.
// Note: We assume no one will ever claim the zero hash here.
if (Claim.unwrap(claims[pivotClaimHash]) != bytes32(0)) {
revert ClaimAlreadyExists();
} }
// Store information about the counterclaim // Bump the parent's reference counter.
// TODO: Good lord, this is a lot of storage usage. Devs do something claimData[challengeIndex].rc += 1;
// Set the parent claim as countered.
// Map `pivotClaimHash` to `pivot` in the `claims` mapping. claimData[challengeIndex].countered = true;
claims[pivotClaimHash] = pivot;
// Compute the position that the claim commits to. Because the parent's position is already
// Map the `pivotClaimHash` to `pivotPos` in the `positions` mapping. // known, we can compute the next position by moving left or right depending on whether
positions[pivotClaimHash] = pivotPos; // or not the move is an attack or defense.
Position nextPosition = isAttack
// Map the `pivotClaimHash` to `claimHash` in the `parents` mapping. ? LibPosition.attack(parent.position)
parents[pivotClaimHash] = claimHash; : LibPosition.defend(parent.position);
// Increment the reference counter for the `claimHash` claim. // Compute the clock for the next claim. The clock's duration is computed by taking the
rc[claimHash] += 1; // difference between the current block timestamp and the parent's clock timestamp.
Clock nextClock = LibClock.wrap(
// Mark `claimHash` as countered. Duration.wrap(
countered[claimHash] = true; uint64(block.timestamp - Timestamp.unwrap(LibClock.timestamp(parent.clock)))
),
// Attempt to inherit grandparent's clock. Timestamp.wrap(uint64(block.timestamp))
ClaimHash claimHashParent = parents[claimHash]; );
// If the grandparent claim doesn't exist, the disagreed upon claim is the root claim. // Enforce the clock time. If the new clock duration is greater than half of the game
// In this case, the mover's clock starts at half the game duration minus the time elapsed since the game started. // duration, then the move is invalid and cannot be made.
if (ClaimHash.unwrap(claimHashParent) == bytes32(0)) { if (Duration.unwrap(LibClock.duration(nextClock)) > Duration.unwrap(GAME_DURATION) >> 1) {
// Calculate the time since the game started revert ClockTimeExceeded();
Duration timeSinceGameStart = Duration.wrap(uint64(block.timestamp - Timestamp.unwrap(gameStart)));
// Set the clock for the pivot point claim.
clocks[pivotClaimHash] = LibClock.wrap(
Duration.wrap((Duration.unwrap(GAME_DURATION) >> 1) - Duration.unwrap(timeSinceGameStart)),
Timestamp.wrap(uint64(block.timestamp))
);
} else {
Clock grandparentClock = clocks[claimHashParent];
Clock parentClock = clocks[claimHash];
// Calculate the remaining clock time for the pivot point claim.
// Grandparent clock time - (block timestamp - parent clock timestamp)
// TODO: Correct this
Clock newClock = LibClock.wrap(
Duration.wrap(
uint64(
Duration.unwrap(LibClock.duration(grandparentClock))
- (block.timestamp - Timestamp.unwrap(LibClock.timestamp(parentClock)))
)
),
Timestamp.wrap(uint64(block.timestamp))
);
// Store the remaining clock time for the pivot point claim.
clocks[pivotClaimHash] = newClock;
} }
// Emit the proper event for other challenge agents to pick up on. // Do not allow for a duplicate claim to be made.
// TODO.
// Maybe map the claimHash? There's no efficient way to check for this with the flat DAG.
// Create the new claim.
claimData.push(
ClaimData({
parentIndex: uint32(challengeIndex),
claim: pivot,
position: nextPosition,
clock: nextClock,
rc: 0,
countered: false
})
);
if (isAttack) { if (isAttack) {
// Emit the `Attack` event for other challenge agents. emit Attack(challengeIndex, pivot, msg.sender);
emit Attack(pivotClaimHash, pivot, msg.sender);
} else { } else {
// Emit the `Defend` event for other challenge agents. emit Defend(challengeIndex, pivot, msg.sender);
emit Defend(pivotClaimHash, pivot, msg.sender);
} }
} }
...@@ -237,65 +196,88 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -237,65 +196,88 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /**
* @notice Initializes the `DisputeGame_Fault` contract. * @inheritdoc IDisputeGame
*/
function initialize() external {
// Grab the root claim from the CWIA calldata.
Claim _rootClaim = rootClaim();
// The root claim is hashed with the root gindex to create the root ClaimHash.
ClaimHash rootClaimHash = LibHashing.hashClaimPos(_rootClaim, ROOT_POSITION);
// The root claim is the first claim in the game.
claims[rootClaimHash] = _rootClaim;
// We do not need to set the position slot for the root claim; It is already zero.
// The root claim's chess clock begins with half of the game duration.
clocks[rootClaimHash] =
LibClock.wrap(Duration.wrap(Duration.unwrap(GAME_DURATION) >> 1), Timestamp.wrap(uint64(block.timestamp)));
// The game starts when the `init()` function is called.
gameStart = Timestamp.wrap(uint64(block.timestamp));
// TODO: Init bond (possibly should be done in the factory.)
}
/**
* @inheritdoc IVersioned
*/
function version() external pure override returns (string memory) {
// TODO: Alias to constant is unnecessary.
return VERSION;
}
/**
* @notice Fetches the game type for the implementation of `IDisputeGame`.
* @dev The reference impl should be entirely different depending on the type (fault, validity)
* i.e. The game type should indicate the security model.
* @return _gameType The type of proof system being used.
*/ */
function gameType() public pure override returns (GameType _gameType) { function gameType() public pure override returns (GameType _gameType) {
_gameType = GameType.FAULT; _gameType = GameType.FAULT;
} }
/** /**
* @notice Returns the timestamp that the DisputeGame contract was created at. * @inheritdoc IDisputeGame
*/ */
function createdAt() external view returns (Timestamp _createdAt) { function createdAt() external view returns (Timestamp _createdAt) {
return gameStart; return gameStart;
} }
/** /**
* @notice If all necessary information has been gathered, this function should mark the game * @inheritdoc IDisputeGame
* status as either `CHALLENGER_WINS` or `DEFENDER_WINS` and return the status of
* the resolved game. It is at this stage that the bonds should be awarded to the
* necessary parties.
* @dev May only be called if the `status` is `IN_PROGRESS`.
*/ */
function resolve() external view returns (GameStatus _status) { function resolve() external returns (GameStatus _status) {
// TODO // The game may only be resolved if it is currently in progress.
return status; if (status != GameStatus.IN_PROGRESS) {
revert GameNotInProgress();
}
// TODO: Block the game from being resolved if the preconditions for resolution have not
// been met.
// Fetch the final index of the claim data DAG.
uint256 i = claimData.length - 1;
// Store a variable on the stack to keep track of the left most, deepest claim found during
// the search.
Position leftMost;
// Run an exhaustive search (`O(n)`) over the DAG to find the left most, deepest
// uncontested claim.
for (; i > 0; i--) {
ClaimData memory claim = claimData[i];
// If the claim has no refereces, we can virtually prune it.
if (claim.rc == 0) {
Position position = claim.position;
uint128 depth = LibPosition.depth(position);
// 1. Do not count nodes at the max game depth. These can be truthy, but they do not
// give us any intuition about the final outcome of the game.
// 2. Any node that has been countered is not a dangling claim, which is all that
// we're concerned about.
// All claims that pass this check qualify for pruning.
if (depth != MAX_GAME_DEPTH && !claim.countered) {
// If the claim here is deeper than the current left most, deepest claim,
// update `leftMost`.
// If the claim here is at the same depth, but further left, update `leftMost`.
if (
depth > LibPosition.depth(leftMost) ||
(depth == LibPosition.depth(leftMost) &&
LibPosition.indexAtDepth(position) <=
LibPosition.indexAtDepth(leftMost))
) {
leftMost = position;
}
}
// If the claim has a parent, decrement the reference count of the parent. This
// effectively "prunes" the claim from the DAG without spending extra gas on
// deleting it from storage.
if (claim.parentIndex != type(uint32).max) {
claimData[claim.parentIndex].rc -= 1;
}
}
}
// If the depth of the left most, deepest dangling claim is odd, the root was attacked
// successfully and the defender wins. Otherwise, the challenger wins.
if (LibPosition.depth(leftMost) % 2 == 0) {
_status = GameStatus.DEFENDER_WINS;
} else {
_status = GameStatus.CHALLENGER_WINS;
}
// Emit the `Resolved` event.
emit Resolved(_status);
// Store the resolved status of the game.
status = _status;
} }
/** /**
...@@ -310,16 +292,52 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -310,16 +292,52 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
*/ */
function extraData() public pure returns (bytes memory _extraData) { function extraData() public pure returns (bytes memory _extraData) {
// The extra data starts at the second word within the cwia calldata. // The extra data starts at the second word within the cwia calldata.
// TODO: What data do we need to pass along to this contract from the factory? Block hash, preimage data, etc.? // TODO: What data do we need to pass along to this contract from the factory?
// Block hash, preimage data, etc.?
_extraData = _getArgDynBytes(0x20, 0x20); _extraData = _getArgDynBytes(0x20, 0x20);
} }
/** /**
* @inheritdoc IDisputeGame * @inheritdoc IDisputeGame
*/ */
function gameData() external pure returns (GameType _gameType, Claim _rootClaim, bytes memory _extraData) { function gameData()
external
pure
returns (
GameType _gameType,
Claim _rootClaim,
bytes memory _extraData
)
{
_gameType = gameType(); _gameType = gameType();
_rootClaim = rootClaim(); _rootClaim = rootClaim();
_extraData = extraData(); _extraData = extraData();
} }
/**
* @inheritdoc IInitializable
*/
function initialize() external {
// Set the game start
gameStart = Timestamp.wrap(uint64(block.timestamp));
// Set the root claim
claimData.push(
ClaimData({
parentIndex: type(uint32).max,
claim: rootClaim(),
position: ROOT_POSITION,
clock: LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))),
rc: 0,
countered: false
})
);
}
/**
* @inheritdoc IVersioned
*/
function version() external pure override returns (string memory) {
return VERSION;
}
} }
...@@ -10,86 +10,51 @@ import { IDisputeGame } from "./IDisputeGame.sol"; ...@@ -10,86 +10,51 @@ import { IDisputeGame } from "./IDisputeGame.sol";
* @notice The interface for a fault proof backed dispute game. * @notice The interface for a fault proof backed dispute game.
*/ */
interface IFaultDisputeGame is IDisputeGame { interface IFaultDisputeGame is IDisputeGame {
/**
* @notice The `ClaimData` struct represents the data associated with a Claim.
* @dev TODO: Pack `Clock` and `Position` into the same slot. Should require 4 64 bit arms.
* @dev TODO: Add bond ID information.
*/
struct ClaimData {
uint32 parentIndex;
uint32 rc;
bool countered;
Claim claim;
Position position;
Clock clock;
}
/** /**
* @notice Emitted when a subclaim is disagreed upon by `claimant` * @notice Emitted when a subclaim is disagreed upon by `claimant`
* @dev Disagreeing with a subclaim is akin to attacking it. * @dev Disagreeing with a subclaim is akin to attacking it.
* @param claimHash The unique ClaimHash that is being disagreed upon * @param parentIndex The index within the `claimData` array of the parent claim
* @param pivot The claim for the following pivot (disagreement = go left) * @param pivot The claim for the following pivot (disagreement = go left)
* @param claimant The address of the claimant * @param claimant The address of the claimant
*/ */
event Attack(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant); event Attack(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
/** /**
* @notice Emitted when a subclaim is agreed upon by `claimant` * @notice Emitted when a subclaim is agreed upon by `claimant`
* @dev Agreeing with a subclaim is akin to defending it. * @dev Agreeing with a subclaim is akin to defending it.
* @param claimHash The unique ClaimHash that is being agreed upon * @param parentIndex The index within the `claimData` array of the parent claim
* @param pivot The claim for the following pivot (agreement = go right) * @param pivot The claim for the following pivot (agreement = go right)
* @param claimant The address of the claimant * @param claimant The address of the claimant
*/ */
event Defend(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant); event Defend(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
/** /**
* @notice Maps a unique ClaimHash to a Claim. * Attack a disagreed upon `Claim`.
* @param claimHash The unique ClaimHash * @param parentIndex Index of the `Claim` to attack in `claimData`.
* @return claim The Claim associated with the ClaimHash * @param pivot The `Claim` at the relative attack position.
*/
function claims(ClaimHash claimHash) external view returns (Claim claim);
/**
* @notice Maps a unique ClaimHash to its parent.
* @param claimHash The unique ClaimHash
* @return parent The parent ClaimHash of the passed ClaimHash
*/
function parents(ClaimHash claimHash) external view returns (ClaimHash parent);
/**
* @notice Maps a unique ClaimHash to its Position.
* @param claimHash The unique ClaimHash
* @return position The Position associated with the ClaimHash
*/
function positions(ClaimHash claimHash) external view returns (Position position);
/**
* @notice Maps a unique ClaimHash to a Bond.
* @param claimHash The unique ClaimHash
* @return bond The Bond associated with the ClaimHash
*/
function bonds(ClaimHash claimHash) external view returns (BondAmount bond);
/**
* @notice Maps a unique ClaimHash its chess clock.
* @param claimHash The unique ClaimHash
* @return clock The chess clock associated with the ClaimHash
*/
function clocks(ClaimHash claimHash) external view returns (Clock clock);
/**
* @notice Maps a unique ClaimHash to its reference counter.
* @param claimHash The unique ClaimHash
* @return _rc The reference counter associated with the ClaimHash
*/
function rc(ClaimHash claimHash) external view returns (uint64 _rc);
/**
* @notice Maps a unique ClaimHash to a boolean indicating whether or not it has been countered.
* @param claimHash The unique claimHash
* @return _countered Whether or not `claimHash` has been countered
*/
function countered(ClaimHash claimHash) external view returns (bool _countered);
/**
* @notice Disagree with a subclaim
* @param disagreement The ClaimHash of the disagreement
* @param pivot The claimed pivot
*/ */
function attack(ClaimHash disagreement, Claim pivot) external; function attack(uint256 parentIndex, Claim pivot) external payable;
/** /**
* @notice Agree with a subclaim * Defend an agreed upon `Claim`.
* @param agreement The ClaimHash of the agreement * @param parentIndex Index of the claim to defend in `claimData`.
* @param pivot The claimed pivot * @param pivot The `Claim` at the relative defense position.
*/ */
function defend(ClaimHash agreement, Claim pivot) external; function defend(uint256 parentIndex, Claim pivot) external payable;
/** /**
* @notice Perform the final step via an on-chain fault proof processor * @notice Perform the final step via an on-chain fault proof processor
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol";
import { FaultDisputeGame } from "../dispute/FaultDisputeGame.sol";
import "../libraries/DisputeTypes.sol";
import { LibClock } from "../dispute/lib/LibClock.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol";
contract FaultDisputeGame_Test is Test {
/**
* @dev The root claim of the game.
*/
Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10)));
/**
* @dev The extra data passed to the game for initialization.
*/
bytes internal constant EXTRA_DATA = abi.encode(1);
/**
* @dev The type of the game being tested.
*/
GameType internal constant GAME_TYPE = GameType.FAULT;
/**
* @dev The current version of the `FaultDisputeGame` contract.
*/
string internal constant VERSION = "0.0.1";
/**
* @dev The factory that will be used to create the game.
*/
DisputeGameFactory internal factory;
/**
* @dev The implementation of the game.
*/
FaultDisputeGame internal gameImpl;
/**
* @dev The `Clone` proxy of the game.
*/
FaultDisputeGame internal gameProxy;
function setUp() public {
// Deploy a new dispute game factory.
factory = new DisputeGameFactory(address(this));
// Deploy an implementation of the fault game
gameImpl = new FaultDisputeGame();
// Register the game implementation with the factory.
factory.setImplementation(GAME_TYPE, gameImpl);
// Create a new game.
gameProxy = FaultDisputeGame(address(factory.create(GAME_TYPE, ROOT_CLAIM, EXTRA_DATA)));
// Label the proxy
vm.label(address(gameProxy), "FaultDisputeGame_Clone");
}
////////////////////////////////////////////////////////////////
// `IDisputeGame` Implementation Tests //
////////////////////////////////////////////////////////////////
/**
* @dev Tests that the game's root claim is set correctly.
*/
function test_rootClaim_succeeds() public {
assertEq(Claim.unwrap(gameProxy.rootClaim()), Claim.unwrap(ROOT_CLAIM));
}
/**
* @dev Tests that the game's extra data is set correctly.
*/
function test_extraData_succeeds() public {
assertEq(gameProxy.extraData(), EXTRA_DATA);
}
/**
* @dev Tests that the game's version is set correctly.
*/
function test_version_succeeds() public {
assertEq(gameProxy.version(), VERSION);
}
/**
* @dev Tests that the game's status is set correctly.
*/
function test_gameStart_succeeds() public {
assertEq(Timestamp.unwrap(gameProxy.gameStart()), block.timestamp);
}
/**
* @dev Tests that the game's type is set correctly.
*/
function test_gameType_succeeds() public {
assertEq(uint256(gameProxy.gameType()), uint256(GAME_TYPE));
}
/**
* @dev Tests that the game's data is set correctly.
*/
function test_gameData_succeeds() public {
(GameType gameType, Claim rootClaim, bytes memory extraData) =
gameProxy.gameData();
assertEq(uint256(gameType), uint256(GAME_TYPE));
assertEq(Claim.unwrap(rootClaim), Claim.unwrap(ROOT_CLAIM));
assertEq(extraData, EXTRA_DATA);
}
////////////////////////////////////////////////////////////////
// `IFaultDisputeGame` Implementation Tests //
////////////////////////////////////////////////////////////////
/**
* @dev Tests that the root claim's data is set correctly when the game is initialized.
*/
function test_initialRootClaimData_succeeds() public {
(
uint32 parentIndex,
uint32 rc,
bool countered,
Claim claim,
Position position,
Clock clock
) = gameProxy.claimData(0);
assertEq(parentIndex, type(uint32).max);
assertEq(rc, 0);
assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM));
assertEq(Position.unwrap(position), 0);
assertEq(Clock.unwrap(clock), Clock.unwrap(LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp)))));
}
/**
* @dev Static unit test for the correctness of an opening attack.
*/
function test_simpleAttack_succeeds() public {
// Warp ahead 5 seconds.
vm.warp(block.timestamp + 5);
// Perform the attack.
gameProxy.attack(0, Claim.wrap(bytes32(uint(5))));
// Grab the claim data of the attack.
(
uint32 parentIndex,
uint32 rc,
bool countered,
Claim claim,
Position position,
Clock clock
) = gameProxy.claimData(1);
// Assert correctness of the attack claim's data.
assertEq(parentIndex, 0);
assertEq(rc, 0);
assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(Claim.wrap(bytes32(uint(5)))));
assertEq(Position.unwrap(position), Position.unwrap(LibPosition.attack(Position.wrap(0))));
assertEq(Clock.unwrap(clock), Clock.unwrap(LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp)))));
// Grab the claim data of the parent.
(
parentIndex,
rc,
countered,
claim,
position,
clock
) = gameProxy.claimData(0);
// Assert correctness of the parent claim's data.
assertEq(parentIndex, type(uint32).max);
assertEq(rc, 1);
assertEq(countered, true);
assertEq(Claim.unwrap(claim), Claim.unwrap(ROOT_CLAIM));
assertEq(Position.unwrap(position), 0);
assertEq(Clock.unwrap(clock), Clock.unwrap(LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp - 5)))));
// Resolve the game.
assertEq(uint256(gameProxy.resolve()), uint256(GameStatus.CHALLENGER_WINS));
}
}
/**
* @title BigStepper
* @notice A mock fault proof processor contract for testing purposes.
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠶⢅⠒⢄⢔⣶⡦⣤⡤⠄⣀⠀⠀⠀⠀⠀⠀⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠨⡏⠀⠀⠈⠢⣙⢯⣄⠀⢨⠯⡺⡘⢄⠀⠀⠀⠀⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣶⡆⠀⠀⠀⠀⠈⠓⠬⡒⠡⣀⢙⡜⡀⠓⠄⠀⠀⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡷⠿⣧⣀⡀⠀⠀⠀⠀⠀⠀⠉⠣⣞⠩⠥⠀⠼⢄⠀⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠉⢹⣶⠒⠒⠂⠈⠉⠁⠘⡆⠀⣿⣿⠫⡄⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢶⣤⣀⡀⠀⠀⢸⡿⠀⠀⠀⠀⠀⢀⠞⠀⠀⢡⢨⢀⡄⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡒⣿⢿⡤⠝⡣⠉⠁⠚⠛⠀⠤⠤⣄⡰⠁⠀⠀⠀⠉⠙⢸⠀⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⢯⡌⡿⡇⠘⡷⠀⠁⠀⠀⢀⣰⠢⠲⠛⣈⣸⠦⠤⠶⠴⢬⣐⣊⡂⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡪⡗⢫⠞⠀⠆⣀⠻⠤⠴⠐⠚⣉⢀⠦⠂⠋⠁⠀⠁⠀⠀⠀⠀⢋⠉⠇⠀
*⠀⠀⠀⠀⣀⡤⠐⠒⠘⡹⠉⢸⠇⠸⠀⠀⠀⠀⣀⣤⠴⠚⠉⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⠀⣾⠀
*⠀⠀⠀⡰⠀⠉⠉⠀⠁⠀⠀⠈⢇⠈⠒⠒⠘⠈⢀⢡⡂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⢸⡄
*⠀⠀⠸⣿⣆⠤⢀⡀⠀⠀⠀⠀⢘⡌⠀⠀⣀⣀⣀⡈⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢸⡇
*⠀⠀⢸⣀⠀⠉⠒⠐⠛⠋⠭⠭⠍⠉⠛⠒⠒⠒⠀⠒⠚⠛⠛⠛⠩⠭⠭⠭⠭⠤⠤⠤⠤⠤⠭⠭⠉⠓⡆
*⠀⠀⠘⠿⣷⣶⣤⣤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇
*⠀⠀⠀⠀⠀⠉⠙⠛⠛⠻⠿⢿⣿⣿⣷⣶⣶⣶⣤⣤⣀⣁⣛⣃⣒⠿⠿⠿⠤⠠⠄⠤⠤⢤⣛⣓⣂⣻⡇
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠙⠛⠻⠿⠿⠿⢿⣿⣿⣿⣷⣶⣶⣾⣿⣿⣿⣿⠿⠟⠁
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀
*/
contract BigStepper {
/**
* @notice Steps from the `preState` to the `postState` by adding 1 to the `preState`.
* @param preState The pre state to start from
* @return postState The state stepped to
*/
function step(Claim preState) external pure returns (Claim postState) {
postState = Claim.wrap(bytes32(uint256(Claim.unwrap(preState)) + 1));
}
}
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