Commit 5fb16e2f authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

Merge pull request #8536 from ethereum-optimism/cl/ctb/ob-actor-tests

feat(ctb): `OutputBisection` actor tests
parents 5b9bff7b 8e658809
This diff is collapsed.
...@@ -13,7 +13,7 @@ const AlphabetVM2StorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\" ...@@ -13,7 +13,7 @@ const AlphabetVM2StorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\"
var AlphabetVM2StorageLayout = new(solc.StorageLayout) var AlphabetVM2StorageLayout = new(solc.StorageLayout)
var AlphabetVM2DeployedBin = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80637dc0d1d01461003b578063e14ced3214610085575b600080fd5b60005461005b9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100986100933660046102d2565b6100a6565b60405190815260200161007c565b600080600060087f0000000000000000000000000000000000000000000000000000000000000000901b600889896040516100e2929190610346565b6040518091039020901b036101c7576000915061010187890189610356565b600080546040517f52f0f3ad000000000000000000000000000000000000000000000000000000008152600480820152602481018890526044810183905260206064820152608481019290925291925073ffffffffffffffffffffffffffffffffffffffff909116906352f0f3ad9060a4016020604051808303816000875af1158015610192573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101b6919061036f565b6101c090826103b7565b90506101e6565b6101d3878901896103cf565b9092509050816101e2816103f1565b9250505b816101f28260016103b7565b604080516020810193909352820152606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001798975050505050505050565b60008083601f84011261029b57600080fd5b50813567ffffffffffffffff8111156102b357600080fd5b6020830191508360208285010111156102cb57600080fd5b9250929050565b6000806000806000606086880312156102ea57600080fd5b853567ffffffffffffffff8082111561030257600080fd5b61030e89838a01610289565b9097509550602088013591508082111561032757600080fd5b5061033488828901610289565b96999598509660400135949350505050565b8183823760009101908152919050565b60006020828403121561036857600080fd5b5035919050565b60006020828403121561038157600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082198211156103ca576103ca610388565b500190565b600080604083850312156103e257600080fd5b50508035926020909101359150565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361042257610422610388565b506001019056fea164736f6c634300080f000a" var AlphabetVM2DeployedBin = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80637dc0d1d01461003b578063e14ced3214610085575b600080fd5b60005461005b9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b610098610093366004610395565b6100a6565b60405190815260200161007c565b600080600060087f0000000000000000000000000000000000000000000000000000000000000000901b600889896040516100e2929190610409565b6040518091039020901b036101d9576000805473ffffffffffffffffffffffffffffffffffffffff1663e03110e161011b60048861029f565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526004810191909152600060248201526044016040805180830381865afa158015610175573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101999190610419565b50640ffffffff0607c82901c169350905063ffffffff608082901c1660006101c38a8c018c61043d565b90506101cf8582610485565b9350505050610206565b6101e58789018961049d565b9092509050816101f4816104bf565b9250508080610202906104bf565b9150505b6040805160208101849052908101829052606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001798975050505050505050565b7f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831617610345818360408051600093845233602052918152606090922091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790565b9392505050565b60008083601f84011261035e57600080fd5b50813567ffffffffffffffff81111561037657600080fd5b60208301915083602082850101111561038e57600080fd5b9250929050565b6000806000806000606086880312156103ad57600080fd5b853567ffffffffffffffff808211156103c557600080fd5b6103d189838a0161034c565b909750955060208801359150808211156103ea57600080fd5b506103f78882890161034c565b96999598509660400135949350505050565b8183823760009101908152919050565b6000806040838503121561042c57600080fd5b505080516020909101519092909150565b60006020828403121561044f57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561049857610498610456565b500190565b600080604083850312156104b057600080fd5b50508035926020909101359150565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036104f0576104f0610456565b506001019056fea164736f6c634300080f000a"
func init() { func init() {
......
This diff is collapsed.
...@@ -100,8 +100,8 @@ ...@@ -100,8 +100,8 @@
"sourceCodeHash": "0xa995b54dce03ddf5c9c47451bd7181996b91398ad66b54ab0b8cbf582863a33e" "sourceCodeHash": "0xa995b54dce03ddf5c9c47451bd7181996b91398ad66b54ab0b8cbf582863a33e"
}, },
"src/dispute/OutputBisectionGame.sol": { "src/dispute/OutputBisectionGame.sol": {
"initCodeHash": "0x6efe83410be6fd58eb07a5297492c6a45598d1f0f84d4ec286d93beade28f40f", "initCodeHash": "0xd354d78579c42a9f0f8a8c9b5ef04f570fa3bd088f88102b431aca8d48fddaae",
"sourceCodeHash": "0x1cc70ebc403581213a4853e1ef1579abeb63496d0625424fa5b9ac8351b2eeca" "sourceCodeHash": "0x98f65f2f2f07a525d360eba87e624f1cb44c52326f3b3f2bf7b6ee41a7ec4a2c"
}, },
"src/legacy/DeployerWhitelist.sol": { "src/legacy/DeployerWhitelist.sol": {
"initCodeHash": "0x8de80fb23b26dd9d849f6328e56ea7c173cd9e9ce1f05c9beea559d1720deb3d", "initCodeHash": "0x8de80fb23b26dd9d849f6328e56ea7c173cd9e9ce1f05c9beea559d1720deb3d",
......
...@@ -83,8 +83,8 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver { ...@@ -83,8 +83,8 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver {
bool internal subgameAtRootResolved; bool internal subgameAtRootResolved;
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 0.0.14 /// @custom:semver 0.0.15
string public constant version = "0.0.14"; string public constant version = "0.0.15";
/// @param _gameType The type ID of the game. /// @param _gameType The type ID of the game.
/// @param _absolutePrestate The absolute prestate of the instruction trace. /// @param _absolutePrestate The absolute prestate of the instruction trace.
...@@ -151,7 +151,7 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver { ...@@ -151,7 +151,7 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver {
// the remainder of the index at depth divided by 2 ** (MAX_GAME_DEPTH - SPLIT_DEPTH), // the remainder of the index at depth divided by 2 ** (MAX_GAME_DEPTH - SPLIT_DEPTH),
// which is the number of leaves in each execution trace subgame. This is so that we can // which is the number of leaves in each execution trace subgame. This is so that we can
// determine whether or not the step position is represents the `ABSOLUTE_PRESTATE`. // determine whether or not the step position is represents the `ABSOLUTE_PRESTATE`.
preStateClaim = (stepPos.indexAtDepth() % (2 ** (MAX_GAME_DEPTH - SPLIT_DEPTH))) == 0 preStateClaim = (stepPos.indexAtDepth() % (1 << (MAX_GAME_DEPTH - SPLIT_DEPTH))) == 0
? ABSOLUTE_PRESTATE ? ABSOLUTE_PRESTATE
: findTraceAncestor(Position.wrap(Position.unwrap(parentPos) - 1), parent.parentIndex, false).claim; : findTraceAncestor(Position.wrap(Position.unwrap(parentPos) - 1), parent.parentIndex, false).claim;
// For all attacks, the poststate is the parent claim. // For all attacks, the poststate is the parent claim.
...@@ -201,28 +201,34 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver { ...@@ -201,28 +201,34 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver {
// INVARIANT: Moves cannot be made unless the game is currently in progress. // INVARIANT: Moves cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
// INVARIANT: A defense can never be made against the root claim. This is because the root
// claim commits to the entire state. Therefore, the only valid defense is to
// do nothing if it is agreed with.
if (_challengeIndex == 0 && !_isAttack) revert CannotDefendRootClaim();
// Get the parent. If it does not exist, the call will revert with OOB. // Get the parent. If it does not exist, the call will revert with OOB.
ClaimData memory parent = claimData[_challengeIndex]; ClaimData memory parent = claimData[_challengeIndex];
// Compute the position that the claim commits to. Because the parent's position is already // Compute the position that the claim commits to. Because the parent's position is already
// known, we can compute the next position by moving left or right depending on whether // known, we can compute the next position by moving left or right depending on whether
// or not the move is an attack or defense. // or not the move is an attack or defense.
Position nextPosition = parent.position.move(_isAttack); Position parentPos = parent.position;
Position nextPosition = parentPos.move(_isAttack);
uint256 nextPositionDepth = nextPosition.depth();
// INVARIANT: A defense can never be made against the root claim of either the output root game or any
// of the execution trace bisection subgames. This is because the root claim commits to the
// entire state. Therefore, the only valid defense is to do nothing if it is agreed with.
if ((_challengeIndex == 0 || nextPositionDepth == SPLIT_DEPTH + 2) && !_isAttack) {
revert CannotDefendRootClaim();
}
// INVARIANT: A move can never surpass the `MAX_GAME_DEPTH`. The only option to counter a // INVARIANT: A move can never surpass the `MAX_GAME_DEPTH`. The only option to counter a
// claim at this depth is to perform a single instruction step on-chain via // claim at this depth is to perform a single instruction step on-chain via
// the `step` function to prove that the state transition produces an unexpected // the `step` function to prove that the state transition produces an unexpected
// post-state. // post-state.
if (nextPosition.depth() > MAX_GAME_DEPTH) revert GameDepthExceeded(); if (nextPositionDepth > MAX_GAME_DEPTH) revert GameDepthExceeded();
// When the next position surpasses the split depth (i.e., it is the root claim of an execution // When the next position surpasses the split depth (i.e., it is the root claim of an execution
// trace bisection sub-game), we need to perform some extra verification steps. // trace bisection sub-game), we need to perform some extra verification steps.
if (nextPosition.depth() == SPLIT_DEPTH + 1) verifyExecBisectionRoot(_claim); if (nextPositionDepth == SPLIT_DEPTH + 1) {
verifyExecBisectionRoot(_claim, _challengeIndex, parentPos, _isAttack);
}
// Fetch the grandparent clock, if it exists. // Fetch the grandparent clock, if it exists.
// The grandparent clock should always exist unless the parent is the root claim. // The grandparent clock should always exist unless the parent is the root claim.
...@@ -317,7 +323,7 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver { ...@@ -317,7 +323,7 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver {
// we add the index at depth + 1 to the genesis block number to get the L2 block number. // we add the index at depth + 1 to the genesis block number to get the L2 block number.
uint256 l2Number = Position.unwrap(startingPos) == 0 uint256 l2Number = Position.unwrap(startingPos) == 0
? GENESIS_BLOCK_NUMBER ? GENESIS_BLOCK_NUMBER
: GENESIS_BLOCK_NUMBER + startingPos.indexAtDepth() + 1; : GENESIS_BLOCK_NUMBER + startingPos.traceIndex(SPLIT_DEPTH) + 1;
oracle.loadLocalData(_ident, Hash.unwrap(uuid), bytes32(l2Number << 0xC0), 8, _partOffset); oracle.loadLocalData(_ident, Hash.unwrap(uuid), bytes32(l2Number << 0xC0), 8, _partOffset);
} else if (_ident == LocalPreimageKey.CHAIN_ID) { } else if (_ident == LocalPreimageKey.CHAIN_ID) {
...@@ -471,11 +477,37 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver { ...@@ -471,11 +477,37 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver {
/// @notice Verifies the integrity of an execution bisection subgame's root claim. Reverts if the claim /// @notice Verifies the integrity of an execution bisection subgame's root claim. Reverts if the claim
/// is invalid. /// is invalid.
/// @param _rootClaim The root claim of the execution bisection subgame. /// @param _rootClaim The root claim of the execution bisection subgame.
function verifyExecBisectionRoot(Claim _rootClaim) internal pure { function verifyExecBisectionRoot(
// The VMStatus must indicate 'invalid' (1), to argue that disputed thing is invalid. Claim _rootClaim,
// Games that agree with the existing outcome are not allowed. uint256 _parentIdx,
Position _parentPos,
bool _isAttack
)
internal
view
{
// The root claim of an execution trace bisection sub-game must:
// 1. Signal that the VM panicked or resulted in an invalid transition if the disputed output root
// was made by the opposing party.
// 2. Signal that the VM resulted in a valid transition if the disputed output root was made by the same party.
// If the move is a defense, the disputed output could have been made by either party. In this case, we
// need to search for the parent output to determine what the expected status byte should be.
Position disputedLeafPos = Position.wrap(Position.unwrap(_parentPos) + 1);
ClaimData storage disputed = findTraceAncestor({ _pos: disputedLeafPos, _start: _parentIdx, _global: true });
uint8 vmStatus = uint8(Claim.unwrap(_rootClaim)[0]); uint8 vmStatus = uint8(Claim.unwrap(_rootClaim)[0]);
if (!(vmStatus == VMStatus.unwrap(VMStatuses.INVALID) || vmStatus == VMStatus.unwrap(VMStatuses.PANIC))) {
if (_isAttack || disputed.position.depth() % 2 == SPLIT_DEPTH % 2) {
// If the move is an attack, the parent output is always deemed to be disputed. In this case, we only need
// to check that the root claim signals that the VM panicked or resulted in an invalid transition.
// If the move is a defense, and the disputed output and creator of the execution trace subgame disagree,
// the root claim should also signal that the VM panicked or resulted in an invalid transition.
if (!(vmStatus == VMStatus.unwrap(VMStatuses.INVALID) || vmStatus == VMStatus.unwrap(VMStatuses.PANIC))) {
revert UnexpectedRootClaim(_rootClaim);
}
} else if (vmStatus != VMStatus.unwrap(VMStatuses.VALID)) {
// The disputed output and the creator of the execution trace subgame agree. The status byte should
// have signaled that the VM succeeded.
revert UnexpectedRootClaim(_rootClaim); revert UnexpectedRootClaim(_rootClaim);
} }
} }
...@@ -496,12 +528,12 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver { ...@@ -496,12 +528,12 @@ contract OutputBisectionGame is IOutputBisectionGame, Clone, ISemver {
returns (ClaimData storage ancestor_) returns (ClaimData storage ancestor_)
{ {
// Grab the trace ancestor's expected position. // Grab the trace ancestor's expected position.
Position preStateTraceAncestor = _global ? _pos.traceAncestor() : _pos.traceAncestorBounded(SPLIT_DEPTH); Position traceAncestorPos = _global ? _pos.traceAncestor() : _pos.traceAncestorBounded(SPLIT_DEPTH);
// Walk up the DAG to find a claim that commits to the same trace index as `_pos`. It is // Walk up the DAG to find a claim that commits to the same trace index as `_pos`. It is
// guaranteed that such a claim exists. // guaranteed that such a claim exists.
ancestor_ = claimData[_start]; ancestor_ = claimData[_start];
while (Position.unwrap(ancestor_.position) != Position.unwrap(preStateTraceAncestor)) { while (Position.unwrap(ancestor_.position) != Position.unwrap(traceAncestorPos)) {
ancestor_ = claimData[ancestor_.parentIndex]; ancestor_ = claimData[ancestor_.parentIndex];
} }
} }
......
...@@ -154,18 +154,8 @@ library LibPosition { ...@@ -154,18 +154,8 @@ library LibPosition {
// This function only works for positions that are below the upper bound. // This function only works for positions that are below the upper bound.
if (_position.depth() <= _upperBoundExclusive) revert ClaimAboveSplit(); if (_position.depth() <= _upperBoundExclusive) revert ClaimAboveSplit();
// Create a field with only the lowest unset bit of `_position` set. // Grab the global trace ancestor.
Position lsb; ancestor_ = traceAncestor(_position);
assembly {
lsb := and(not(_position), add(_position, 1))
}
// Find the index of the lowest unset bit within the field.
uint256 msb = lsb.depth();
assembly {
let a := shr(msb, _position)
// Bound the ancestor to the minimum gindex, 1.
ancestor_ := or(a, iszero(a))
}
// If the ancestor is above or at the upper bound, shift it to be below the upper bound. // If the ancestor is above or at the upper bound, shift it to be below the upper bound.
// This should be a special case that only covers positions that commit to the final leaf // This should be a special case that only covers positions that commit to the final leaf
......
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol"; import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol";
import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; import { PreimageOracle, PreimageKeyLib } from "src/cannon/PreimageOracle.sol";
import "src/libraries/DisputeTypes.sol"; import "src/libraries/DisputeTypes.sol";
/// @title AlphabetVM2 /// @title AlphabetVM2
/// @dev A mock VM for the purpose of testing the dispute game infrastructure. /// @dev A mock VM for the purpose of testing the dispute game infrastructure. Note that this only works
/// for games with an execution trace subgame max depth of 3 (8 instructions per subgame).
contract AlphabetVM2 is IBigStepper { contract AlphabetVM2 is IBigStepper {
Claim internal immutable ABSOLUTE_PRESTATE; Claim internal immutable ABSOLUTE_PRESTATE;
IPreimageOracle public oracle; IPreimageOracle public oracle;
...@@ -23,23 +24,29 @@ contract AlphabetVM2 is IBigStepper { ...@@ -23,23 +24,29 @@ contract AlphabetVM2 is IBigStepper {
bytes32 _localContext bytes32 _localContext
) )
external external
view
returns (bytes32 postState_) returns (bytes32 postState_)
{ {
uint256 traceIndex; uint256 traceIndex;
uint256 claim; uint256 claim;
if ((keccak256(_stateData) << 8) == (Claim.unwrap(ABSOLUTE_PRESTATE) << 8)) { if ((keccak256(_stateData) << 8) == (Claim.unwrap(ABSOLUTE_PRESTATE) << 8)) {
// If the state data is empty, then the absolute prestate is the claim. // If the state data is empty, then the absolute prestate is the claim.
traceIndex = 0; (bytes32 dat,) = oracle.readPreimage(
(claim) = abi.decode(_stateData, (uint256)); PreimageKeyLib.localizeIdent(LocalPreimageKey.STARTING_L2_BLOCK_NUMBER, _localContext), 0
claim = claim + uint256(oracle.loadLocalData(4, _localContext, 0, 32, 0)); );
uint256 startingL2BlockNumber = (uint256(dat) >> 128) & 0xFFFFFFFF;
traceIndex = startingL2BlockNumber << 4;
(uint256 absolutePrestateClaim) = abi.decode(_stateData, (uint256));
claim = absolutePrestateClaim + traceIndex;
} else { } else {
// Otherwise, decode the state data. // Otherwise, decode the state data.
(traceIndex, claim) = abi.decode(_stateData, (uint256, uint256)); (traceIndex, claim) = abi.decode(_stateData, (uint256, uint256));
traceIndex++; traceIndex++;
claim++;
} }
// STF: n -> n + 1 // STF: n -> n + 1
postState_ = keccak256(abi.encode(traceIndex, claim + 1)); postState_ = keccak256(abi.encode(traceIndex, claim));
assembly { assembly {
postState_ := or(and(postState_, not(shl(248, 0xFF))), shl(248, 1)) postState_ := or(and(postState_, not(shl(248, 0xFF))), shl(248, 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