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
507ddd2c
Commit
507ddd2c
authored
Jun 10, 2023
by
clabby
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Start simple bisection
parent
93a02de0
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
426 additions
and
228 deletions
+426
-228
FaultDisputeGame.sol
.../contracts-bedrock/contracts/dispute/FaultDisputeGame.sol
+185
-167
IFaultDisputeGame.sol
...edrock/contracts/dispute/interfaces/IFaultDisputeGame.sol
+26
-61
FaultDisputeGame.t.sol
...s/contracts-bedrock/contracts/test/FaultDisputeGame.t.sol
+215
-0
No files found.
packages/contracts-bedrock/contracts/dispute/FaultDisputeGame.sol
View file @
507ddd2c
...
@@ -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 c
laimHash The claim hash that the move is being made
against.
* @param c
hallengeIndex 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 p
osition of the move.
// Get the p
arent
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;
}
}
}
packages/contracts-bedrock/contracts/dispute/interfaces/IFaultDisputeGame.sol
View file @
507ddd2c
...
@@ -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
...
...
packages/contracts-bedrock/contracts/test/FaultDisputeGame.t.sol
0 → 100644
View file @
507ddd2c
// 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));
}
}
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