Commit 8a879c41 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #5984 from ethereum-optimism/clabby/ctb/start-bisection-game

feat(ctb): Start bisection game
parents c7949cb3 84e8b55b
......@@ -32,6 +32,14 @@ DisputeGameFactory_Test:test_setImplementation_notOwner_reverts() (gas: 16099)
DisputeGameFactory_Test:test_setImplementation_succeeds() (gas: 44302)
DisputeGameFactory_Test:test_transferOwnership_notOwner_reverts() (gas: 15974)
DisputeGameFactory_Test:test_transferOwnership_succeeds() (gas: 18694)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17456)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17882)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10315)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8237)
FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17602)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8213)
FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 105058)
FaultDisputeGame_Test:test_version_succeeds() (gas: 9758)
FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950342)
......
......@@ -20,44 +20,44 @@ interface IDisputeGame is IInitializable, IVersioned {
/**
* @notice Returns the timestamp that the DisputeGame contract was created at.
* @return _createdAt The timestamp that the DisputeGame contract was created at.
* @return createdAt_ The timestamp that the DisputeGame contract was created at.
*/
function createdAt() external view returns (Timestamp _createdAt);
function createdAt() external view returns (Timestamp createdAt_);
/**
* @notice Returns the current status of the game.
* @return _status The current status of the game.
* @return status_ The current status of the game.
*/
function status() external view returns (GameStatus _status);
function status() external view returns (GameStatus status_);
/**
* @notice Getter for the game type.
* @dev `clones-with-immutable-args` argument #1
* @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.
* @return gameType_ The type of proof system being used.
*/
function gameType() external pure returns (GameType _gameType);
function gameType() external pure returns (GameType gameType_);
/**
* @notice Getter for the root claim.
* @dev `clones-with-immutable-args` argument #2
* @return _rootClaim The root claim of the DisputeGame.
* @return rootClaim_ The root claim of the DisputeGame.
*/
function rootClaim() external pure returns (Claim _rootClaim);
function rootClaim() external pure returns (Claim rootClaim_);
/**
* @notice Getter for the extra data.
* @dev `clones-with-immutable-args` argument #3
* @return _extraData Any extra data supplied to the dispute game contract by the creator.
* @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/
function extraData() external pure returns (bytes memory _extraData);
function extraData() external pure returns (bytes memory extraData_);
/**
* @notice Returns the address of the `BondManager` used.
* @return _bondManager The address of the `BondManager` used.
* @return bondManager_ The address of the `BondManager` used.
*/
function bondManager() external view returns (IBondManager _bondManager);
function bondManager() external view returns (IBondManager bondManager_);
/**
* @notice If all necessary information has been gathered, this function should mark the game
......@@ -65,22 +65,25 @@ interface IDisputeGame is IInitializable, IVersioned {
* 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`.
* @return _status The status of the game after resolution.
* @return status_ The status of the game after resolution.
*/
function resolve() external returns (GameStatus _status);
function resolve() external returns (GameStatus status_);
/**
* @notice A compliant implementation of this interface should return the components of the
* game UUID's preimage provided in the cwia payload. The preimage of the UUID is
* constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes
* concatenation.
* @return gameType_ The type of proof system being used.
* @return rootClaim_ The root claim of the DisputeGame.
* @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/
function gameData()
external
pure
returns (
GameType _gameType,
Claim _rootClaim,
bytes memory _extraData
GameType gameType_,
Claim rootClaim_,
bytes memory extraData_
);
}
......@@ -11,85 +11,39 @@ import { IDisputeGame } from "./IDisputeGame.sol";
*/
interface IFaultDisputeGame is IDisputeGame {
/**
* @notice Emitted when a subclaim is disagreed upon by `claimant`
* @dev Disagreeing with a subclaim is akin to attacking it.
* @param claimHash The unique ClaimHash that is being disagreed upon
* @param pivot The claim for the following pivot (disagreement = go left)
* @param claimant The address of the claimant
* @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.
*/
event Attack(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant);
struct ClaimData {
uint32 parentIndex;
bool countered;
Claim claim;
Position position;
Clock clock;
}
/**
* @notice Emitted when a subclaim is agreed upon by `claimant`
* @dev Agreeing with a subclaim is akin to defending it.
* @param claimHash The unique ClaimHash that is being agreed upon
* @param pivot The claim for the following pivot (agreement = go right)
* @notice Emitted when a new claim is added to the DAG by `claimant`
* @param parentIndex The index within the `claimData` array of the parent claim
* @param pivot The claim being added
* @param claimant The address of the claimant
*/
event Defend(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant);
/**
* @notice Maps a unique ClaimHash to a Claim.
* @param claimHash The unique ClaimHash
* @return claim The Claim associated with the ClaimHash
*/
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);
event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
/**
* @notice Disagree with a subclaim
* @param disagreement The ClaimHash of the disagreement
* @param pivot The claimed pivot
* @notice Attack a disagreed upon `Claim`.
* @param _parentIndex Index of the `Claim` to attack in `claimData`.
* @param _pivot The `Claim` at the relative attack position.
*/
function attack(ClaimHash disagreement, Claim pivot) external;
function attack(uint256 _parentIndex, Claim _pivot) external payable;
/**
* @notice Agree with a subclaim
* @param agreement The ClaimHash of the agreement
* @param pivot The claimed pivot
* @notice Defend an agreed upon `Claim`.
* @param _parentIndex Index of the claim to defend in `claimData`.
* @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
......@@ -97,7 +51,15 @@ interface IFaultDisputeGame is IDisputeGame {
* a step in the fault proof program on-chain. The interface of the fault proof
* processor contract should be generic enough such that we can use different
* fault proof VMs (MIPS, RiscV5, etc.)
* @param disagreement The ClaimHash of the disagreement
* @param _prestateIndex The index of the prestate of the step within `claimData`.
* @param _parentIndex The index of the parent claim within `claimData`.
* @param _stateData The stateData of the step is the preimage of the claim @ `prestateIndex`
* @param _proof Proof to access memory leaf nodes in the VM.
*/
function step(ClaimHash disagreement) external;
function step(
uint256 _prestateIndex,
uint256 _parentIndex,
bytes calldata _stateData,
bytes calldata _proof
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title LibClock
* @notice This library contains helper functions for working with the `Clock` type.
*/
library LibClock {
/**
* @notice Packs a `Duration` and `Timestamp` into a `Clock` type.
* @param _duration The `Duration` to pack into the `Clock` type.
* @param _timestamp The `Timestamp` to pack into the `Clock` type.
* @return clock_ The `Clock` containing the `_duration` and `_timestamp`.
*/
function wrap(Duration _duration, Timestamp _timestamp) internal pure returns (Clock clock_) {
assembly {
clock_ := or(shl(0x40, _duration), _timestamp)
}
}
/**
* @notice Pull the `Duration` out of a `Clock` type.
* @param _clock The `Clock` type to pull the `Duration` out of.
* @return duration_ The `Duration` pulled out of `_clock`.
*/
function duration(Clock _clock) internal pure returns (Duration duration_) {
// Shift the high-order 64 bits into the low-order 64 bits, leaving only the `duration`.
assembly {
duration_ := shr(0x40, _clock)
}
}
/**
* @notice Pull the `Timestamp` out of a `Clock` type.
* @param _clock The `Clock` type to pull the `Timestamp` out of.
* @return timestamp_ The `Timestamp` pulled out of `_clock`.
*/
function timestamp(Clock _clock) internal pure returns (Timestamp timestamp_) {
// Clean the high-order 192 bits by shifting the clock left and then right again, leaving
// only the `timestamp`.
assembly {
timestamp_ := shr(0xC0, shl(0xC0, _clock))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title Hashing
* @notice This library contains all of the hashing utilities used in the Cannon contracts.
*/
library LibHashing {
/**
* @notice Hashes a claim and a position together.
* @param _claim A Claim type.
* @param _position The position of `claim`.
* @return claimHash_ A hash of abi.encodePacked(claim, position);
*/
function hashClaimPos(Claim _claim, Position _position) internal pure returns (ClaimHash claimHash_) {
assembly {
mstore(0x00, _claim)
mstore(0x20, _position)
claimHash_ := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title LibPosition
* @notice This library contains helper functions for working with the `Position` type.
*/
library LibPosition {
function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) {
assembly {
position_ := or(shl(0x40, _depth), _indexAtDepth)
}
}
/**
* @notice Pulls the `depth` out of a packed `Position` type.
* @param _position The position to get the `depth` of.
* @return depth_ The `depth` of the `position`.
*/
function depth(Position _position) internal pure returns (uint64 depth_) {
// Shift the high-order 64 bits into the low-order 64 bits, leaving only the `depth`.
assembly {
depth_ := shr(0x40, _position)
}
}
/**
* @notice Pulls the `indexAtDepth` out of a packed `Position` type.
* @param _position The position to get the `indexAtDepth` of.
* @return indexAtDepth_ The `indexAtDepth` of the `position`.
*/
function indexAtDepth(Position _position) internal pure returns (uint64 indexAtDepth_) {
// Clean the high-order 192 bits by shifting the position left and then right again, leaving
// only the `indexAtDepth`.
assembly {
indexAtDepth_ := shr(0xC0, shl(0xC0, _position))
}
}
/**
* @notice Get the position to the left of `position`.
* @param _position The position to get the left position of.
* @return left_ The position to the left of `position`.
*/
function left(Position _position) internal pure returns (Position left_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Left = { depth: position.depth + 1, indexAtDepth: position.indexAtDepth * 2 }
assembly {
left_ := or(shl(0x40, add(_depth, 0x01)), shl(0x01, _indexAtDepth))
}
}
/**
* @notice Get the position to the right of `position`.
* @param _position The position to get the right position of.
* @return right_ The position to the right of `position`.
*/
function right(Position _position) internal pure returns (Position right_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Right = { depth: position.depth + 1, indexAtDepth: position.indexAtDepth * 2 + 1 }
assembly {
right_ := or(shl(0x40, add(_depth, 0x01)), add(shl(0x01, _indexAtDepth), 0x01))
}
}
/**
* @notice Get the parent position of `position`.
* @param _position The position to get the parent position of.
* @return parent_ The parent position of `position`.
*/
function parent(Position _position) internal pure returns (Position parent_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Parent = { depth: position.depth - 1, indexAtDepth: position.indexAtDepth / 2 }
assembly {
parent_ := or(shl(0x40, sub(_depth, 0x01)), shr(0x01, _indexAtDepth))
}
}
/**
* @notice Get the deepest, right most index relative to the `position`.
* @param _position The position to get the relative deepest, right most index of.
* @param _maxDepth The maximum depth of the game.
* @return rightIndex_ The deepest, right most index relative to the `position`.
*/
function rightIndex(Position _position, uint256 _maxDepth) internal pure returns (uint64 rightIndex_) {
assembly {
rightIndex_ := shr(0xC0, shl(0xC0, _position))
// Walk down to the max depth by moving right
for { let i := shr(0x40, _position) } lt(i, sub(_maxDepth, 0x01)) { i := add(i, 0x01) } {
rightIndex_ := add(0x01, shl(0x01, rightIndex_))
}
}
}
/**
* @notice Get the attack position relative to `position`.
* @param _position The position to get the relative attack position of.
* @return attack_ The attack position relative to `position`.
*/
function attack(Position _position) internal pure returns (Position attack_) {
return left(_position);
}
/**
* @notice Get the defense position relative to `position`.
* @param _position The position to get the relative defense position of.
* @return defense_ The defense position relative to `position`.
*/
function defend(Position _position) internal pure returns (Position defense_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Defend = { depth: position.depth + 1, indexAtDepth: ((position.indexAtDepth / 2) * 2 + 1) * 2 }
assembly {
defense_ := or(shl(0x40, add(_depth, 0x01)), shl(0x01, add(0x01, shl(0x01, shr(0x01, _indexAtDepth)))))
}
}
}
......@@ -39,6 +39,32 @@ error CannotDefendRootClaim();
*/
error ClaimAlreadyExists();
/**
* @notice Thrown when a given claim is invalid (0).
*/
error InvalidClaim();
/**
* @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when
* the game is not in progress.
*/
error GameNotInProgress();
/**
* @notice Thrown when a move is attempted to be made against a claim that does not exist.
*/
error ParentDoesNotExist();
/**
* @notice Thrown when a move is attempted to be made after the clock has timed out.
*/
error ClockTimeExceeded();
/**
* @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
*/
error GameDepthExceeded();
////////////////////////////////////////////////////////////////
// `AttestationDisputeGame` Errors //
////////////////////////////////////////////////////////////////
......
......@@ -39,11 +39,11 @@ type Duration is uint64;
* ┌────────────┬────────────────┐
* │ Bits │ Value │
* ├────────────┼────────────────┤
* │ [0, 128) │ Duration │
* │ [128, 256) │ Timestamp │
* │ [0, 64) │ Duration │
* │ [64, 128) │ Timestamp │
* └────────────┴────────────────┘
*/
type Clock is uint256;
type Clock is uint128;
/**
* @notice A `Position` represents a position of a claim within the game tree.
......@@ -51,11 +51,11 @@ type Clock is uint256;
* ┌────────────┬────────────────┐
* │ Bits │ Value │
* ├────────────┼────────────────┤
* │ [0, 128) │ Depth │
* │ [128, 256) │ Index at depth │
* │ [0, 64) │ Depth │
* │ [64, 128) │ Index at depth │
* └────────────┴────────────────┘
*/
type Position is uint256;
type Position is uint128;
/**
* @notice A `GameType` represents the type of game being played.
......
// 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.wrap(0);
/**
* @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();
factory.initialize(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(GameType.unwrap(gameProxy.gameType()), GameType.unwrap(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(GameType.unwrap(gameType), GameType.unwrap(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,
bool countered,
Claim claim,
Position position,
Clock clock
) = gameProxy.claimData(0);
assertEq(parentIndex, type(uint32).max);
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(uint256(5))));
// Grab the claim data of the attack.
(
uint32 parentIndex,
bool countered,
Claim claim,
Position position,
Clock clock
) = gameProxy.claimData(1);
// Assert correctness of the attack claim's data.
assertEq(parentIndex, 0);
assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(Claim.wrap(bytes32(uint256(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, countered, claim, position, clock) = gameProxy.claimData(0);
// Assert correctness of the parent claim's data.
assertEq(parentIndex, type(uint32).max);
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)))
)
);
}
}
/**
* @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));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { LibClock } from "../dispute/lib/LibClock.sol";
import "../libraries/DisputeTypes.sol";
/**
* @notice Tests for `LibClock`
*/
contract LibClock_Test is Test {
/**
* @notice Tests that the `duration` function correctly shifts out the `Duration` from a packed `Clock` type.
*/
function testFuzz_duration_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Duration.unwrap(LibClock.duration(clock)), Duration.unwrap(_duration));
}
/**
* @notice Tests that the `timestamp` function correctly shifts out the `Timestamp` from a packed `Clock` type.
*/
function testFuzz_timestamp_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Timestamp.unwrap(LibClock.timestamp(clock)), Timestamp.unwrap(_timestamp));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol";
import "../libraries/DisputeTypes.sol";
/**
* @notice Tests for `LibPosition`
*/
contract LibPosition_Test is Test {
/**
* @dev Assumes a MAX depth of 63 for the Position type. Any greater depth can cause overflows.
* @dev At the lowest level of the tree, this allows for 2 ** 63 leaves. In reality, the max game depth
* will likely be much lower.
*/
uint8 internal constant MAX_DEPTH = 63;
/**
* @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type.
*/
function testFuzz_depth_correctness(uint64 _depth, uint64 _indexAtDepth) public {
Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(LibPosition.depth(position), _depth);
}
/**
* @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` type.
*/
function testFuzz_indexAtDepth_correctness(uint64 _depth, uint64 _indexAtDepth) public {
Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(LibPosition.indexAtDepth(position), _indexAtDepth);
}
/**
* @notice Tests that the `left` function correctly computes the position of the left child.
*/
function testFuzz_left_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position left = LibPosition.left(position);
assertEq(LibPosition.depth(left), uint64(_depth) + 1);
assertEq(LibPosition.indexAtDepth(left), _indexAtDepth * 2);
}
/**
* @notice Tests that the `right` function correctly computes the position of the right child.
*/
function testFuzz_right_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position right = LibPosition.right(position);
assertEq(LibPosition.depth(right), _depth + 1);
assertEq(LibPosition.indexAtDepth(right), _indexAtDepth * 2 + 1);
}
/**
* @notice Tests that the `parent` function correctly computes the position of the parent.
*/
function testFuzz_parent_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position parent = LibPosition.parent(position);
assertEq(LibPosition.depth(parent), _depth - 1);
assertEq(LibPosition.indexAtDepth(parent), _indexAtDepth / 2);
}
/**
* @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative
* to a given position.
*/
function testFuzz_rightIndex_correctness(
uint8 _maxDepth,
uint8 _depth,
uint64 _indexAtDepth
) public {
// Max depth bound: [1, 63]
// The max game depth MUST be at least 1.
_maxDepth = uint8(bound(_maxDepth, 1, MAX_DEPTH));
// Depth bound: [0, _maxDepth]
_depth = uint8(bound(_depth, 0, _maxDepth));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth);
uint64 rightIndex = LibPosition.rightIndex(position, _maxDepth);
// Find the deepest, rightmost index in Solidity rather than Yul
for (uint256 i = _depth; i < _maxDepth - 1; ++i) {
position = LibPosition.right(position);
}
uint64 _rightIndex = LibPosition.indexAtDepth(position);
assertEq(rightIndex, _rightIndex);
}
/**
* @notice Tests that the `attack` function correctly computes the position of the attack relative to
* a given position.
* @dev `attack` is an alias for `left`, but we test it separately for completeness.
*/
function testFuzz_attack_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position attack = LibPosition.attack(position);
assertEq(LibPosition.depth(attack), _depth + 1);
assertEq(LibPosition.indexAtDepth(attack), _indexAtDepth * 2);
}
/**
* @notice Tests that the `defend` function correctly computes the position of the defense relative to
* a given position.
* @dev A defense can only be given if the position does not belong to the root claim, hence the bound of [1, 127]
* on the depth.
*/
function testFuzz_defend_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH));
// Index at depth bound: [0, 2 ** _depth]
_indexAtDepth = uint64(bound(_indexAtDepth, 0, 2**_depth));
Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position defend = LibPosition.defend(position);
assertEq(LibPosition.depth(defend), _depth + 1);
assertEq(LibPosition.indexAtDepth(defend), ((_indexAtDepth / 2) * 2 + 1) * 2);
}
}
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