Commit a95bd8c2 authored by clabby's avatar clabby

Add modular VM interface + improve `OneVsOne` arena

parent cbae9e68
......@@ -32,12 +32,12 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert
DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 497198)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 499064)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot:test_resolvesCorrectly_succeeds() (gas: 489092)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 494067)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 495933)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot:test_resolvesCorrectly_succeeds() (gas: 485961)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 513436)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 515302)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot:test_resolvesCorrectly_succeeds() (gas: 502966)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 510311)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 512177)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot:test_resolvesCorrectly_succeeds() (gas: 499841)
FaultDisputeGame_Test:test_defendRoot_invalidMove_reverts() (gas: 13250)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17409)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17834)
......
......@@ -6,6 +6,7 @@ import { IVersioned } from "./interfaces/IVersioned.sol";
import { IFaultDisputeGame } from "./interfaces/IFaultDisputeGame.sol";
import { IInitializable } from "./interfaces/IInitializable.sol";
import { IBondManager } from "./interfaces/IBondManager.sol";
import { IBigStepper } from "./interfaces/IBigStepper.sol";
import { Clone } from "../libraries/Clone.sol";
import { LibHashing } from "./lib/LibHashing.sol";
......@@ -29,6 +30,10 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
/// @notice The max depth of the game.
uint256 public immutable MAX_GAME_DEPTH;
/// @notice A hypervisor that performs single instruction steps on a fault proof program trace.
/// @return vm_ The address of the hypervisor contract.
IBigStepper public immutable VM;
/// @notice The duration of the game.
/// @dev TODO: Account for resolution buffer. (?)
Duration internal constant GAME_DURATION = Duration.wrap(7 days);
......@@ -55,9 +60,14 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
mapping(ClaimHash => bool) internal claims;
/// @param _absolutePrestate The absolute prestate of the instruction trace.
constructor(Claim _absolutePrestate, uint256 _maxGameDepth) {
constructor(
Claim _absolutePrestate,
uint256 _maxGameDepth,
IBigStepper _vm
) {
ABSOLUTE_PRESTATE = _absolutePrestate;
MAX_GAME_DEPTH = _maxGameDepth;
VM = _vm;
}
////////////////////////////////////////////////////////////////
......@@ -79,8 +89,8 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
uint256 _stateIndex,
uint256 _claimIndex,
bool _isAttack,
bytes calldata,
bytes calldata
bytes calldata _stateData,
bytes calldata _proof
) external {
// Steps cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) {
......@@ -127,19 +137,19 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
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 (
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();
}
}
// TODO: Call `MIPS.sol#step` to verify the step.
// For now, we just use a simple state transition function that increments the prestate,
// `s_p`, by 1.
if (uint256(Claim.unwrap(preStateClaim)) + 1 == uint256(Claim.unwrap(postStateClaim))) {
// Perform the VM step and check to see if it is valid.
if (VM.step(_stateData, _proof) == Claim.unwrap(postStateClaim)) {
revert ValidStep();
}
......
// 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_);
}
......@@ -11,6 +11,8 @@ import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol";
import { LibClock } from "../dispute/lib/LibClock.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol";
import { Bytes } from "../libraries/Bytes.sol";
import { IBigStepper } from "../dispute/interfaces/IBigStepper.sol";
contract FaultDisputeGame_Init is DisputeGameFactory_Init {
/// @dev The extra data passed to the game for initialization.
......@@ -28,7 +30,7 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init {
function init(Claim rootClaim, Claim absolutePrestate) public {
super.setUp();
// Deploy an implementation of the fault game
gameImpl = new FaultDisputeGame(absolutePrestate, 4);
gameImpl = new FaultDisputeGame(absolutePrestate, 4, new BigStepper(absolutePrestate));
// Register the game implementation with the factory.
factory.setImplementation(GAME_TYPE, gameImpl);
// Create a new game.
......@@ -296,10 +298,10 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
contract GamePlayer {
bool public failedToStep;
FaultDisputeGame public gameProxy;
bytes public trace;
GamePlayer internal counterParty;
Vm internal vm;
bytes internal trace;
uint256 internal maxDepth;
/// @notice Initializes the player
......@@ -307,7 +309,7 @@ contract GamePlayer {
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual {
) public {
gameProxy = _gameProxy;
counterParty = _counterParty;
vm = _vm;
......@@ -366,8 +368,10 @@ contract GamePlayer {
// If we are past the maximum depth, break the recursion and step.
if (movePos.depth() > maxDepth) {
// Perform a step.
uint256 stateIndex;
Position statePos;
bytes memory preStateTrace;
// 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
// move position is 0, the prestate is the absolute prestate and we need to
......@@ -376,7 +380,7 @@ contract GamePlayer {
Position leafPos = isAttack
? Position.wrap(Position.unwrap(parentPos) - 1)
: Position.wrap(Position.unwrap(parentPos) + 1);
Position statePos = leafPos;
statePos = leafPos;
// Walk up until the valid position that commits to the prestate's
// trace index is found.
......@@ -397,10 +401,13 @@ contract GamePlayer {
break;
}
}
// Grab the trace up to the prestate's trace index.
preStateTrace = traceAt(isAttack ? statePos : parentPos);
}
// 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.
} catch {
failedToStep = true;
......@@ -439,14 +446,24 @@ contract GamePlayer {
return uint256(vm.load(address(gameProxy), bytes32(uint256(1))));
}
/// @notice Returns the player's claim that commits to a given gindex.
function claimAt(Position _position) internal view returns (Claim claim_) {
return claimAt(_position.rightIndex(maxDepth).indexAtDepth());
/// @notice Returns the preimage of a player's claim that commits to a given trace index.
function traceAt(Position _position) public view returns (bytes memory trace_) {
return traceAt(_position.rightIndex(maxDepth).indexAtDepth());
}
/// @notice Returns the preimage of a player's claim that commits to a given trace index.
function traceAt(uint256 _traceIndex) public view returns (bytes memory trace_) {
return Bytes.slice(trace, 0, _traceIndex + 1);
}
/// @notice Returns the player's claim that commits to a given trace index.
function claimAt(uint256 _traceIndex) public view returns (Claim claim_) {
return Claim.wrap(bytes32(uint256(bytes32(trace[_traceIndex]) >> 248)));
return Claim.wrap(keccak256(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.rightIndex(maxDepth).indexAtDepth());
}
}
......@@ -481,9 +498,9 @@ contract OneVsOne_Arena is FaultDisputeGame_Init {
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new FullyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(30))));
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new FullyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(keccak256(dishonest.trace())));
}
function test_resolvesCorrectly_succeeds() public {
......@@ -499,9 +516,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new FullyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31))));
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new FullyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(keccak256(honest.trace())));
}
function test_resolvesCorrectly_succeeds() public {
......@@ -517,9 +534,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new HalfDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(15))));
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new HalfDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(keccak256(dishonest.trace())));
}
function test_resolvesCorrectly_succeeds() public {
......@@ -535,9 +552,9 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena {
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new HalfDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31))));
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new HalfDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(keccak256(honest.trace())));
}
function test_resolvesCorrectly_succeeds() public {
......@@ -553,9 +570,9 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena {
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new EarlyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(15))));
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new EarlyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(keccak256(dishonest.trace())));
}
function test_resolvesCorrectly_succeeds() public {
......@@ -569,11 +586,11 @@ contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena {
}
}
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot4 is OneVsOne_Arena {
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot3 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new EarlyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31))));
GamePlayer honest = new HonestPlayer(ABSOLUTE_PRESTATE);
GamePlayer dishonest = new EarlyDivergentPlayer(ABSOLUTE_PRESTATE);
super.init(honest, dishonest, Claim.wrap(keccak256(honest.trace())));
}
function test_resolvesCorrectly_succeeds() public {
......@@ -592,13 +609,8 @@ contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot4 is OneVsOne_Arena {
////////////////////////////////////////////////////////////////
contract HonestPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
constructor(Claim _absolutePrestate) {
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
bytes memory honestTrace = new bytes(16);
for (uint8 i = 0; i < honestTrace.length; i++) {
honestTrace[i] = bytes1(absolutePrestate + i + 1);
......@@ -608,13 +620,8 @@ contract HonestPlayer is GamePlayer {
}
contract FullyDivergentPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
constructor(Claim _absolutePrestate) {
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the honest trace by 1.
......@@ -625,13 +632,8 @@ contract FullyDivergentPlayer is GamePlayer {
}
contract HalfDivergentPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
constructor(Claim _absolutePrestate) {
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first half.
......@@ -642,13 +644,8 @@ contract HalfDivergentPlayer is GamePlayer {
}
contract EarlyDivergentPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
constructor(Claim _absolutePrestate) {
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_absolutePrestate)));
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first half.
......@@ -657,3 +654,31 @@ contract EarlyDivergentPlayer is GamePlayer {
trace = dishonestTrace;
}
}
////////////////////////////////////////////////////////////////
// MOCK VMS //
////////////////////////////////////////////////////////////////
contract BigStepper 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_)
{
// STF: n -> concat(n, n[n.len-1] + 1)
uint8 postState = (
_stateData.length == 0
? uint8(uint256(Claim.unwrap(ABSOLUTE_PRESTATE)))
: uint8(_stateData[_stateData.length - 1])
) + 1;
// Hash the trace for the claim.
postState_ = keccak256(bytes.concat(_stateData, bytes1(postState)));
}
}
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