Commit 590d2b79 authored by clabby's avatar clabby Committed by GitHub

Merge pull request #6112 from ethereum-optimism/clabby/dispute/hotswap-vm

feat(ctb): Modular VM for `FaultDisputeGame`
parents e4985632 9d68af5b
...@@ -32,12 +32,12 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert ...@@ -32,12 +32,12 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert
DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243) DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950) DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642) DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 497198) FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 502842)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 499064) FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 504699)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot:test_resolvesCorrectly_succeeds() (gas: 489092) FaultDisputeGame_ResolvesCorrectly_CorrectRoot:test_resolvesCorrectly_succeeds() (gas: 492706)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 494067) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 501717)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 495933) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 503574)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot:test_resolvesCorrectly_succeeds() (gas: 485961) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot:test_resolvesCorrectly_succeeds() (gas: 491581)
FaultDisputeGame_Test:test_defendRoot_invalidMove_reverts() (gas: 13250) FaultDisputeGame_Test:test_defendRoot_invalidMove_reverts() (gas: 13250)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17409) FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17409)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17834) FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17834)
...@@ -49,10 +49,10 @@ FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103231) ...@@ -49,10 +49,10 @@ FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103231)
FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 407967) FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 407967)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10923) FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10923)
FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24632) FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24632)
FaultDisputeGame_Test:test_resolve_challengeContested() (gas: 221074) FaultDisputeGame_Test:test_resolve_challengeContested() (gas: 221068)
FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9657) FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9657)
FaultDisputeGame_Test:test_resolve_rootContested() (gas: 106120) FaultDisputeGame_Test:test_resolve_rootContested() (gas: 106120)
FaultDisputeGame_Test:test_resolve_rootUncontested() (gas: 23630) FaultDisputeGame_Test:test_resolve_rootUncontested() (gas: 23624)
FaultDisputeGame_Test:test_resolve_teamDeathmatch() (gas: 391731) FaultDisputeGame_Test:test_resolve_teamDeathmatch() (gas: 391731)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8203) FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8203)
FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 107322) FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 107322)
......
...@@ -6,6 +6,7 @@ import { IVersioned } from "./interfaces/IVersioned.sol"; ...@@ -6,6 +6,7 @@ import { IVersioned } from "./interfaces/IVersioned.sol";
import { IFaultDisputeGame } from "./interfaces/IFaultDisputeGame.sol"; import { IFaultDisputeGame } from "./interfaces/IFaultDisputeGame.sol";
import { IInitializable } from "./interfaces/IInitializable.sol"; import { IInitializable } from "./interfaces/IInitializable.sol";
import { IBondManager } from "./interfaces/IBondManager.sol"; import { IBondManager } from "./interfaces/IBondManager.sol";
import { IBigStepper } from "./interfaces/IBigStepper.sol";
import { Clone } from "../libraries/Clone.sol"; import { Clone } from "../libraries/Clone.sol";
import { LibHashing } from "./lib/LibHashing.sol"; import { LibHashing } from "./lib/LibHashing.sol";
...@@ -29,6 +30,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -29,6 +30,9 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
/// @notice The max depth of the game. /// @notice The max depth of the game.
uint256 public immutable MAX_GAME_DEPTH; uint256 public immutable MAX_GAME_DEPTH;
/// @notice A hypervisor that performs single instruction steps on a fault proof program trace.
IBigStepper public immutable VM;
/// @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);
...@@ -55,9 +59,14 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -55,9 +59,14 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
mapping(ClaimHash => bool) internal claims; mapping(ClaimHash => bool) internal claims;
/// @param _absolutePrestate The absolute prestate of the instruction trace. /// @param _absolutePrestate The absolute prestate of the instruction trace.
constructor(Claim _absolutePrestate, uint256 _maxGameDepth) { constructor(
Claim _absolutePrestate,
uint256 _maxGameDepth,
IBigStepper _vm
) {
ABSOLUTE_PRESTATE = _absolutePrestate; ABSOLUTE_PRESTATE = _absolutePrestate;
MAX_GAME_DEPTH = _maxGameDepth; MAX_GAME_DEPTH = _maxGameDepth;
VM = _vm;
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
...@@ -79,8 +88,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -79,8 +88,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
uint256 _stateIndex, uint256 _stateIndex,
uint256 _claimIndex, uint256 _claimIndex,
bool _isAttack, bool _isAttack,
bytes calldata, bytes calldata _stateData,
bytes calldata bytes calldata _proof
) external { ) external {
// Steps cannot be made unless the game is currently in progress. // Steps cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) { if (status != GameStatus.IN_PROGRESS) {
...@@ -127,19 +136,19 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -127,19 +136,19 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
postStateClaim = claimData[_stateIndex].claim; postStateClaim = claimData[_stateIndex].claim;
} }
// Assert that the given prestate commits to the instruction at `gindex - 1`. // Assert that the given prestate commits to the instruction at `gindex - 1` and
// that the `_stateData` is the preimage for the prestate claim digest.
if ( if (
Position.unwrap(preStatePos.rightIndex(MAX_GAME_DEPTH)) != Position.unwrap(preStatePos.rightIndex(MAX_GAME_DEPTH)) !=
Position.unwrap(postStatePos.rightIndex(MAX_GAME_DEPTH)) - 1 Position.unwrap(postStatePos.rightIndex(MAX_GAME_DEPTH)) - 1 ||
keccak256(_stateData) != Claim.unwrap(preStateClaim)
) { ) {
revert InvalidPrestate(); revert InvalidPrestate();
} }
} }
// TODO: Call `MIPS.sol#step` to verify the step. // Perform the VM step and check to see if it is valid.
// For now, we just use a simple state transition function that increments the prestate, if (VM.step(_stateData, _proof) == Claim.unwrap(postStateClaim)) {
// `s_p`, by 1.
if (uint256(Claim.unwrap(preStateClaim)) + 1 == uint256(Claim.unwrap(postStateClaim))) {
revert ValidStep(); revert ValidStep();
} }
...@@ -274,7 +283,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -274,7 +283,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
// Search for the left-most dangling non-bottom node // Search for the left-most dangling non-bottom node
// The most recent claim is always a dangling, non-bottom node so we start with that // The most recent claim is always a dangling, non-bottom node so we start with that
uint256 leftMostIndex = claimData.length - 1; uint256 leftMostIndex = claimData.length - 1;
Position leftMostTraceIndex = Position.wrap(type(uint128).max); uint256 leftMostTraceIndex = type(uint128).max;
for (uint256 i = leftMostIndex; i < type(uint64).max; ) { for (uint256 i = leftMostIndex; i < type(uint64).max; ) {
// Fetch the claim at the current index. // Fetch the claim at the current index.
ClaimData storage claim = claimData[i]; ClaimData storage claim = claimData[i];
...@@ -295,8 +304,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -295,8 +304,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
// 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.
Position traceIndex = claimPos.rightIndex(MAX_GAME_DEPTH); uint256 traceIndex = claimPos.traceIndex(MAX_GAME_DEPTH);
if (Position.unwrap(traceIndex) < Position.unwrap(leftMostTraceIndex)) { if (traceIndex < leftMostTraceIndex) {
leftMostTraceIndex = traceIndex; leftMostTraceIndex = traceIndex;
unchecked { unchecked {
leftMostIndex = i + 1; leftMostIndex = i + 1;
...@@ -309,7 +318,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -309,7 +318,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
if ( if (
// slither-disable-next-line weak-prng // slither-disable-next-line weak-prng
claimData[leftMostIndex].position.depth() % 2 == 0 && claimData[leftMostIndex].position.depth() % 2 == 0 &&
Position.unwrap(leftMostTraceIndex) != type(uint128).max leftMostTraceIndex != type(uint128).max
) { ) {
status_ = GameStatus.DEFENDER_WINS; status_ = GameStatus.DEFENDER_WINS;
} else { } else {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/// @title IBigStepper
/// @notice An interface for a contract with a state transition function that
/// will accept a pre state and return a post state.
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠶⢅⠒⢄⢔⣶⡦⣤⡤⠄⣀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠨⡏⠀⠀⠈⠢⣙⢯⣄⠀⢨⠯⡺⡘⢄⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣶⡆⠀⠀⠀⠀⠈⠓⠬⡒⠡⣀⢙⡜⡀⠓⠄⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡷⠿⣧⣀⡀⠀⠀⠀⠀⠀⠀⠉⠣⣞⠩⠥⠀⠼⢄⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠉⢹⣶⠒⠒⠂⠈⠉⠁⠘⡆⠀⣿⣿⠫⡄⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢶⣤⣀⡀⠀⠀⢸⡿⠀⠀⠀⠀⠀⢀⠞⠀⠀⢡⢨⢀⡄⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡒⣿⢿⡤⠝⡣⠉⠁⠚⠛⠀⠤⠤⣄⡰⠁⠀⠀⠀⠉⠙⢸⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⢯⡌⡿⡇⠘⡷⠀⠁⠀⠀⢀⣰⠢⠲⠛⣈⣸⠦⠤⠶⠴⢬⣐⣊⡂⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡪⡗⢫⠞⠀⠆⣀⠻⠤⠴⠐⠚⣉⢀⠦⠂⠋⠁⠀⠁⠀⠀⠀⠀⢋⠉⠇⠀
/// ⠀⠀⠀⠀⣀⡤⠐⠒⠘⡹⠉⢸⠇⠸⠀⠀⠀⠀⣀⣤⠴⠚⠉⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⠀⣾⠀
/// ⠀⠀⠀⡰⠀⠉⠉⠀⠁⠀⠀⠈⢇⠈⠒⠒⠘⠈⢀⢡⡂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⢸⡄
/// ⠀⠀⠸⣿⣆⠤⢀⡀⠀⠀⠀⠀⢘⡌⠀⠀⣀⣀⣀⡈⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢸⡇
/// ⠀⠀⢸⣀⠀⠉⠒⠐⠛⠋⠭⠭⠍⠉⠛⠒⠒⠒⠀⠒⠚⠛⠛⠛⠩⠭⠭⠭⠭⠤⠤⠤⠤⠤⠭⠭⠉⠓⡆
/// ⠀⠀⠘⠿⣷⣶⣤⣤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇
/// ⠀⠀⠀⠀⠀⠉⠙⠛⠛⠻⠿⢿⣿⣿⣷⣶⣶⣶⣤⣤⣀⣁⣛⣃⣒⠿⠿⠿⠤⠠⠄⠤⠤⢤⣛⣓⣂⣻⡇
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠙⠛⠻⠿⠿⠿⢿⣿⣿⣿⣷⣶⣶⣾⣿⣿⣿⣿⠿⠟⠁
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀
interface IBigStepper {
/// @notice Performs a single instruction step from a given prestate and returns the poststate
/// hash.
/// @param _stateData The preimage of the prestate hash.
/// @param _proof A proof for the inclusion of the prestate's memory in the merkle tree.
/// @return postState_ The poststate hash after the instruction step.
function step(bytes calldata _stateData, bytes calldata _proof)
external
returns (bytes32 postState_);
}
...@@ -104,6 +104,26 @@ library LibPosition { ...@@ -104,6 +104,26 @@ library LibPosition {
} }
} }
/// @notice Get the deepest, right most trace index relative to the `position`. This is
/// equivalent to calling `right` on a position until the maximum depth is reached and
/// then finding its index at depth.
/// @param _position The position to get the relative trace index of.
/// @param _maxDepth The maximum depth of the game.
/// @return traceIndex_ The trace index relative to the `position`.
function traceIndex(
Position _position,
uint256 _maxDepth
) internal pure returns (uint256 traceIndex_) {
uint256 msb = depth(_position);
assembly {
let remaining := sub(_maxDepth, msb)
traceIndex_ := sub(
or(shl(remaining, _position), sub(shl(remaining, 1), 1)),
shl(_maxDepth, 1)
)
}
}
/// @notice Get the move position of `_position`, which is the left child of: /// @notice Get the move position of `_position`, which is the left child of:
/// 1. `_position + 1` if `_isAttack` is true. /// 1. `_position + 1` if `_isAttack` is true.
/// 1. `_position` if `_isAttack` is false. /// 1. `_position` if `_isAttack` is false.
......
...@@ -11,6 +11,7 @@ import "../libraries/DisputeTypes.sol"; ...@@ -11,6 +11,7 @@ import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol"; import "../libraries/DisputeErrors.sol";
import { LibClock } from "../dispute/lib/LibClock.sol"; import { LibClock } from "../dispute/lib/LibClock.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol"; import { LibPosition } from "../dispute/lib/LibPosition.sol";
import { IBigStepper } from "../dispute/interfaces/IBigStepper.sol";
contract FaultDisputeGame_Init is DisputeGameFactory_Init { contract FaultDisputeGame_Init is DisputeGameFactory_Init {
/// @dev The extra data passed to the game for initialization. /// @dev The extra data passed to the game for initialization.
...@@ -28,7 +29,7 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init { ...@@ -28,7 +29,7 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init {
function init(Claim rootClaim, Claim absolutePrestate) public { function init(Claim rootClaim, Claim absolutePrestate) public {
super.setUp(); super.setUp();
// Deploy an implementation of the fault game // Deploy an implementation of the fault game
gameImpl = new FaultDisputeGame(absolutePrestate, 4); gameImpl = new FaultDisputeGame(absolutePrestate, 4, new AlphabetVM(absolutePrestate));
// Register the game implementation with the factory. // Register the game implementation with the factory.
factory.setImplementation(GAME_TYPE, gameImpl); factory.setImplementation(GAME_TYPE, gameImpl);
// Create a new game. // Create a new game.
...@@ -296,10 +297,10 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -296,10 +297,10 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
contract GamePlayer { contract GamePlayer {
bool public failedToStep; bool public failedToStep;
FaultDisputeGame public gameProxy; FaultDisputeGame public gameProxy;
bytes public trace;
GamePlayer internal counterParty; GamePlayer internal counterParty;
Vm internal vm; Vm internal vm;
bytes internal trace;
uint256 internal maxDepth; uint256 internal maxDepth;
/// @notice Initializes the player /// @notice Initializes the player
...@@ -307,7 +308,7 @@ contract GamePlayer { ...@@ -307,7 +308,7 @@ contract GamePlayer {
FaultDisputeGame _gameProxy, FaultDisputeGame _gameProxy,
GamePlayer _counterParty, GamePlayer _counterParty,
Vm _vm Vm _vm
) public virtual { ) public {
gameProxy = _gameProxy; gameProxy = _gameProxy;
counterParty = _counterParty; counterParty = _counterParty;
vm = _vm; vm = _vm;
...@@ -366,8 +367,9 @@ contract GamePlayer { ...@@ -366,8 +367,9 @@ contract GamePlayer {
// If we are past the maximum depth, break the recursion and step. // If we are past the maximum depth, break the recursion and step.
if (movePos.depth() > maxDepth) { if (movePos.depth() > maxDepth) {
// Perform a step.
uint256 stateIndex; uint256 stateIndex;
bytes memory preStateTrace;
// First, we need to find the pre/post state index depending on whether we // First, we need to find the pre/post state index depending on whether we
// are making an attack step or a defense step. If the index at depth of the // are making an attack step or a defense step. If the index at depth of the
// move position is 0, the prestate is the absolute prestate and we need to // move position is 0, the prestate is the absolute prestate and we need to
...@@ -397,17 +399,24 @@ contract GamePlayer { ...@@ -397,17 +399,24 @@ contract GamePlayer {
break; break;
} }
} }
// Grab the trace up to the prestate's trace index.
if (isAttack) {
preStateTrace = abi.encode(statePos.traceIndex(maxDepth), traceAt(statePos));
} else {
preStateTrace = abi.encode(parentPos.traceIndex(maxDepth), traceAt(parentPos));
}
} }
// Perform the step and halt recursion. // Perform the step and halt recursion.
try gameProxy.step(stateIndex, _parentIndex, isAttack, hex"", hex"") { try gameProxy.step(stateIndex, _parentIndex, isAttack, preStateTrace, hex"") {
// Do nothing, step succeeded. // Do nothing, step succeeded.
} catch { } catch {
failedToStep = true; failedToStep = true;
} }
} else { } else {
// Find the trace index that our next claim must commit to. // Find the trace index that our next claim must commit to.
uint256 traceIndex = movePos.rightIndex(maxDepth).indexAtDepth(); uint256 traceIndex = movePos.traceIndex(maxDepth);
// Grab the claim that we need to make from the helper. // Grab the claim that we need to make from the helper.
Claim ourClaim = claimAt(traceIndex); Claim ourClaim = claimAt(traceIndex);
...@@ -439,151 +448,156 @@ contract GamePlayer { ...@@ -439,151 +448,156 @@ contract GamePlayer {
return uint256(vm.load(address(gameProxy), bytes32(uint256(1)))); return uint256(vm.load(address(gameProxy), bytes32(uint256(1))));
} }
/// @notice Returns the player's claim that commits to a given gindex. /// @notice Returns the state at the trace index within the player's trace.
function claimAt(Position _position) internal view returns (Claim claim_) { function traceAt(Position _position) public view returns (uint256 state_) {
return claimAt(_position.rightIndex(maxDepth).indexAtDepth()); return traceAt(_position.traceIndex(maxDepth));
}
/// @notice Returns the state at the trace index within the player's trace.
function traceAt(uint256 _traceIndex) public view returns (uint256 state_) {
return uint256(uint8(trace[_traceIndex]));
} }
/// @notice Returns the player's claim that commits to a given trace index. /// @notice Returns the player's claim that commits to a given trace index.
function claimAt(uint256 _traceIndex) public view returns (Claim claim_) { function claimAt(uint256 _traceIndex) public view returns (Claim claim_) {
return Claim.wrap(bytes32(uint256(bytes32(trace[_traceIndex]) >> 248))); return Claim.wrap(keccak256(abi.encode(_traceIndex, traceAt(_traceIndex))));
}
/// @notice Returns the player's claim that commits to a given trace index.
function claimAt(Position _position) public view returns (Claim claim_) {
return claimAt(_position.traceIndex(maxDepth));
} }
} }
contract OneVsOne_Arena is FaultDisputeGame_Init { contract OneVsOne_Arena is FaultDisputeGame_Init {
/// @dev The absolute prestate of the trace. /// @dev The absolute prestate of the trace.
Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(15))); Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(15)));
/// @dev The honest participant. /// @dev The defender.
GamePlayer internal honest; GamePlayer internal defender;
/// @dev The dishonest participant. /// @dev The challenger.
GamePlayer internal dishonest; GamePlayer internal challenger;
function init( function init(GamePlayer _defender, GamePlayer _challenger) public {
GamePlayer _honest, Claim rootClaim = Claim.wrap(keccak256(abi.encode(15, _defender.traceAt(15))));
GamePlayer _dishonest, super.init(rootClaim, ABSOLUTE_PRESTATE);
Claim _rootClaim defender = _defender;
) public { challenger = _challenger;
super.init(_rootClaim, ABSOLUTE_PRESTATE);
// Deploy a new honest player.
honest = _honest;
// Deploy a new dishonest player.
dishonest = _dishonest;
// Set the counterparties. // Set the counterparties.
honest.init(gameProxy, dishonest, vm); defender.init(gameProxy, challenger, vm);
dishonest.init(gameProxy, honest, vm); challenger.init(gameProxy, defender, vm);
// Label actors for trace. // Label actors for trace.
vm.label(address(honest), "HonestPlayer"); vm.label(address(challenger), "Challenger");
vm.label(address(dishonest), "DishonestPlayer"); vm.label(address(defender), "Defender");
} }
} }
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena { contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
function setUp() public override { function setUp() public override {
GamePlayer honest = new HonestPlayer(); GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new FullyDivergentPlayer(); GamePlayer dishonest = new FullyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(30)))); super.init(dishonest, honest);
} }
function test_resolvesCorrectly_succeeds() public { function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced. // Play the game until a step is forced.
honest.play(0); challenger.play(0);
// 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));
assertFalse(honest.failedToStep()); assertFalse(defender.failedToStep());
} }
} }
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena { contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
function setUp() public override { function setUp() public override {
GamePlayer honest = new HonestPlayer(); GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new FullyDivergentPlayer(); GamePlayer dishonest = new FullyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31)))); super.init(honest, dishonest);
} }
function test_resolvesCorrectly_succeeds() public { function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced. // Play the game until a step is forced.
dishonest.play(0); challenger.play(0);
// 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));
assertTrue(dishonest.failedToStep()); assertTrue(challenger.failedToStep());
} }
} }
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena { contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena {
function setUp() public override { function setUp() public override {
GamePlayer honest = new HonestPlayer(); GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new HalfDivergentPlayer(); GamePlayer dishonest = new HalfDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(15)))); super.init(dishonest, honest);
} }
function test_resolvesCorrectly_succeeds() public { function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced. // Play the game until a step is forced.
honest.play(0); challenger.play(0);
// 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));
assertFalse(honest.failedToStep()); assertFalse(defender.failedToStep());
} }
} }
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena { contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena {
function setUp() public override { function setUp() public override {
GamePlayer honest = new HonestPlayer(); GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new HalfDivergentPlayer(); GamePlayer dishonest = new HalfDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31)))); super.init(honest, dishonest);
} }
function test_resolvesCorrectly_succeeds() public { function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced. // Play the game until a step is forced.
dishonest.play(0); challenger.play(0);
// 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));
assertTrue(dishonest.failedToStep()); assertTrue(challenger.failedToStep());
} }
} }
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena { contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena {
function setUp() public override { function setUp() public override {
GamePlayer honest = new HonestPlayer(); GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new EarlyDivergentPlayer(); GamePlayer dishonest = new EarlyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(15)))); super.init(dishonest, honest);
} }
function test_resolvesCorrectly_succeeds() public { function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced. // Play the game until a step is forced.
honest.play(0); challenger.play(0);
// 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));
assertFalse(honest.failedToStep()); assertFalse(defender.failedToStep());
} }
} }
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot4 is OneVsOne_Arena { contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot3 is OneVsOne_Arena {
function setUp() public override { function setUp() public override {
GamePlayer honest = new HonestPlayer(); GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new EarlyDivergentPlayer(); GamePlayer dishonest = new EarlyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31)))); super.init(honest, dishonest);
} }
function test_resolvesCorrectly_succeeds() public { function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced. // Play the game until a step is forced.
dishonest.play(0); challenger.play(0);
// 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));
assertTrue(dishonest.failedToStep()); assertTrue(challenger.failedToStep());
} }
} }
...@@ -592,13 +606,8 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot4 is OneVsOne_Arena { ...@@ -592,13 +606,8 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot4 is OneVsOne_Arena {
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
contract HonestPlayer is GamePlayer { contract HonestPlayer is GamePlayer {
function init( constructor(Claim _absolutePrestate) {
FaultDisputeGame _gameProxy, uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory honestTrace = new bytes(16); bytes memory honestTrace = new bytes(16);
for (uint8 i = 0; i < honestTrace.length; i++) { for (uint8 i = 0; i < honestTrace.length; i++) {
honestTrace[i] = bytes1(absolutePrestate + i + 1); honestTrace[i] = bytes1(absolutePrestate + i + 1);
...@@ -608,13 +617,8 @@ contract HonestPlayer is GamePlayer { ...@@ -608,13 +617,8 @@ contract HonestPlayer is GamePlayer {
} }
contract FullyDivergentPlayer is GamePlayer { contract FullyDivergentPlayer is GamePlayer {
function init( constructor(Claim _absolutePrestate) {
FaultDisputeGame _gameProxy, uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory dishonestTrace = new bytes(16); bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) { for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the honest trace by 1. // Offset the honest trace by 1.
...@@ -625,13 +629,8 @@ contract FullyDivergentPlayer is GamePlayer { ...@@ -625,13 +629,8 @@ contract FullyDivergentPlayer is GamePlayer {
} }
contract HalfDivergentPlayer is GamePlayer { contract HalfDivergentPlayer is GamePlayer {
function init( constructor(Claim _absolutePrestate) {
FaultDisputeGame _gameProxy, uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory dishonestTrace = new bytes(16); bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) { for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first half. // Offset the trace after the first half.
...@@ -642,13 +641,8 @@ contract HalfDivergentPlayer is GamePlayer { ...@@ -642,13 +641,8 @@ contract HalfDivergentPlayer is GamePlayer {
} }
contract EarlyDivergentPlayer is GamePlayer { contract EarlyDivergentPlayer is GamePlayer {
function init( constructor(Claim _absolutePrestate) {
FaultDisputeGame _gameProxy, uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory dishonestTrace = new bytes(16); bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) { for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first half. // Offset the trace after the first half.
...@@ -657,3 +651,36 @@ contract EarlyDivergentPlayer is GamePlayer { ...@@ -657,3 +651,36 @@ contract EarlyDivergentPlayer is GamePlayer {
trace = dishonestTrace; trace = dishonestTrace;
} }
} }
////////////////////////////////////////////////////////////////
// MOCK VMS //
////////////////////////////////////////////////////////////////
contract AlphabetVM is IBigStepper {
Claim internal immutable ABSOLUTE_PRESTATE;
constructor(Claim _absolutePrestate) {
ABSOLUTE_PRESTATE = _absolutePrestate;
}
/// @inheritdoc IBigStepper
function step(bytes calldata _stateData, bytes calldata)
external
view
returns (bytes32 postState_)
{
uint256 traceIndex;
uint256 claim;
if (_stateData.length == 0) {
// If the state data is empty, then the absolute prestate is the claim.
traceIndex = 0;
claim = uint256(Claim.unwrap(ABSOLUTE_PRESTATE));
} else {
// Otherwise, decode the state data.
(traceIndex, claim) = abi.decode(_stateData, (uint256, uint256));
traceIndex++;
}
// STF: n -> n + 1
postState_ = keccak256(abi.encode(traceIndex, claim + 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