github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; 5 6 import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; 7 import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; 8 import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; 9 import { IInitializable } from "src/dispute/interfaces/IInitializable.sol"; 10 import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol"; 11 import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; 12 13 import { Clone } from "src/libraries/Clone.sol"; 14 import { Types } from "src/libraries/Types.sol"; 15 import { ISemver } from "src/universal/ISemver.sol"; 16 import { LibClock } from "src/dispute/lib/LibUDT.sol"; 17 18 import "src/libraries/DisputeTypes.sol"; 19 import "src/libraries/DisputeErrors.sol"; 20 21 /// @title FaultDisputeGame 22 /// @notice An implementation of the `IFaultDisputeGame` interface. 23 contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver { 24 //////////////////////////////////////////////////////////////// 25 // State Vars // 26 //////////////////////////////////////////////////////////////// 27 28 /// @notice The absolute prestate of the instruction trace. This is a constant that is defined 29 /// by the program that is being used to execute the trace. 30 Claim internal immutable ABSOLUTE_PRESTATE; 31 32 /// @notice The max depth of the game. 33 uint256 internal immutable MAX_GAME_DEPTH; 34 35 /// @notice The max depth of the output bisection portion of the position tree. Immediately beneath 36 /// this depth, execution trace bisection begins. 37 uint256 internal immutable SPLIT_DEPTH; 38 39 /// @notice The duration of the game. 40 Duration internal immutable GAME_DURATION; 41 42 /// @notice An onchain VM that performs single instruction steps on a fault proof program trace. 43 IBigStepper internal immutable VM; 44 45 /// @notice The game type ID. 46 GameType internal immutable GAME_TYPE; 47 48 /// @notice WETH contract for holding ETH. 49 IDelayedWETH internal immutable WETH; 50 51 /// @notice The anchor state registry. 52 IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; 53 54 /// @notice The chain ID of the L2 network this contract argues about. 55 uint256 internal immutable L2_CHAIN_ID; 56 57 /// @notice The global root claim's position is always at gindex 1. 58 Position internal constant ROOT_POSITION = Position.wrap(1); 59 60 /// @notice The flag set in the `bond` field of a `ClaimData` struct to indicate that the bond has been claimed. 61 uint128 internal constant CLAIMED_BOND_FLAG = type(uint128).max; 62 63 /// @notice The starting timestamp of the game 64 Timestamp public createdAt; 65 66 /// @notice The timestamp of the game's global resolution. 67 Timestamp public resolvedAt; 68 69 /// @inheritdoc IDisputeGame 70 GameStatus public status; 71 72 /// @notice An append-only array of all claims made during the dispute game. 73 ClaimData[] public claimData; 74 75 /// @notice Credited balances for winning participants. 76 mapping(address => uint256) public credit; 77 78 /// @notice An internal mapping to allow for constant-time lookups of existing claims. 79 mapping(ClaimHash => bool) internal claims; 80 81 /// @notice An internal mapping of subgames rooted at a claim index to other claim indices in the subgame. 82 mapping(uint256 => uint256[]) internal subgames; 83 84 /// @notice Indicates whether the subgame rooted at the root claim has been resolved. 85 bool internal subgameAtRootResolved; 86 87 /// @notice Flag for the `initialize` function to prevent re-initialization. 88 bool internal initialized; 89 90 /// @notice The latest finalized output root, serving as the anchor for output bisection. 91 OutputRoot public startingOutputRoot; 92 93 /// @notice Semantic version. 94 /// @custom:semver 0.8.1 95 string public constant version = "0.8.1"; 96 97 /// @param _gameType The type ID of the game. 98 /// @param _absolutePrestate The absolute prestate of the instruction trace. 99 /// @param _maxGameDepth The maximum depth of bisection. 100 /// @param _splitDepth The final depth of the output bisection portion of the game. 101 /// @param _gameDuration The duration of the game. 102 /// @param _vm An onchain VM that performs single instruction steps on an FPP trace. 103 /// @param _weth WETH contract for holding ETH. 104 /// @param _anchorStateRegistry The contract that stores the anchor state for each game type. 105 /// @param _l2ChainId Chain ID of the L2 network this contract argues about. 106 constructor( 107 GameType _gameType, 108 Claim _absolutePrestate, 109 uint256 _maxGameDepth, 110 uint256 _splitDepth, 111 Duration _gameDuration, 112 IBigStepper _vm, 113 IDelayedWETH _weth, 114 IAnchorStateRegistry _anchorStateRegistry, 115 uint256 _l2ChainId 116 ) { 117 // The split depth cannot be greater than or equal to the max game depth. 118 if (_splitDepth >= _maxGameDepth) revert InvalidSplitDepth(); 119 120 GAME_TYPE = _gameType; 121 ABSOLUTE_PRESTATE = _absolutePrestate; 122 MAX_GAME_DEPTH = _maxGameDepth; 123 SPLIT_DEPTH = _splitDepth; 124 GAME_DURATION = _gameDuration; 125 VM = _vm; 126 WETH = _weth; 127 ANCHOR_STATE_REGISTRY = _anchorStateRegistry; 128 L2_CHAIN_ID = _l2ChainId; 129 } 130 131 /// @notice Receive function to allow the contract to receive ETH. 132 receive() external payable { } 133 134 /// @notice Fallback function to allow the contract to receive ETH. 135 fallback() external payable { } 136 137 //////////////////////////////////////////////////////////////// 138 // `IFaultDisputeGame` impl // 139 //////////////////////////////////////////////////////////////// 140 141 /// @inheritdoc IFaultDisputeGame 142 function step( 143 uint256 _claimIndex, 144 bool _isAttack, 145 bytes calldata _stateData, 146 bytes calldata _proof 147 ) 148 public 149 virtual 150 { 151 // INVARIANT: Steps cannot be made unless the game is currently in progress. 152 if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); 153 154 // Get the parent. If it does not exist, the call will revert with OOB. 155 ClaimData storage parent = claimData[_claimIndex]; 156 157 // Pull the parent position out of storage. 158 Position parentPos = parent.position; 159 // Determine the position of the step. 160 Position stepPos = parentPos.move(_isAttack); 161 162 // INVARIANT: A step cannot be made unless the move position is 1 below the `MAX_GAME_DEPTH` 163 if (stepPos.depth() != MAX_GAME_DEPTH + 1) revert InvalidParent(); 164 165 // Determine the expected pre & post states of the step. 166 Claim preStateClaim; 167 ClaimData storage postState; 168 if (_isAttack) { 169 // If the step position's index at depth is 0, the prestate is the absolute 170 // prestate. 171 // If the step is an attack at a trace index > 0, the prestate exists elsewhere in 172 // the game state. 173 // NOTE: We localize the `indexAtDepth` for the current execution trace subgame by finding 174 // the remainder of the index at depth divided by 2 ** (MAX_GAME_DEPTH - SPLIT_DEPTH), 175 // which is the number of leaves in each execution trace subgame. This is so that we can 176 // determine whether or not the step position is represents the `ABSOLUTE_PRESTATE`. 177 preStateClaim = (stepPos.indexAtDepth() % (1 << (MAX_GAME_DEPTH - SPLIT_DEPTH))) == 0 178 ? ABSOLUTE_PRESTATE 179 : _findTraceAncestor(Position.wrap(parentPos.raw() - 1), parent.parentIndex, false).claim; 180 // For all attacks, the poststate is the parent claim. 181 postState = parent; 182 } else { 183 // If the step is a defense, the poststate exists elsewhere in the game state, 184 // and the parent claim is the expected pre-state. 185 preStateClaim = parent.claim; 186 postState = _findTraceAncestor(Position.wrap(parentPos.raw() + 1), parent.parentIndex, false); 187 } 188 189 // INVARIANT: The prestate is always invalid if the passed `_stateData` is not the 190 // preimage of the prestate claim hash. 191 // We ignore the highest order byte of the digest because it is used to 192 // indicate the VM Status and is added after the digest is computed. 193 if (keccak256(_stateData) << 8 != preStateClaim.raw() << 8) revert InvalidPrestate(); 194 195 // Compute the local preimage context for the step. 196 Hash uuid = _findLocalContext(_claimIndex); 197 198 // INVARIANT: If a step is an attack, the poststate is valid if the step produces 199 // the same poststate hash as the parent claim's value. 200 // If a step is a defense: 201 // 1. If the parent claim and the found post state agree with each other 202 // (depth diff % 2 == 0), the step is valid if it produces the same 203 // state hash as the post state's claim. 204 // 2. If the parent claim and the found post state disagree with each other 205 // (depth diff % 2 != 0), the parent cannot be countered unless the step 206 // produces the same state hash as `postState.claim`. 207 // SAFETY: While the `attack` path does not need an extra check for the post 208 // state's depth in relation to the parent, we don't need another 209 // branch because (n - n) % 2 == 0. 210 bool validStep = VM.step(_stateData, _proof, uuid.raw()) == postState.claim.raw(); 211 bool parentPostAgree = (parentPos.depth() - postState.position.depth()) % 2 == 0; 212 if (parentPostAgree == validStep) revert ValidStep(); 213 214 // INVARIANT: A step cannot be made against a claim for a second time. 215 if (parent.counteredBy != address(0)) revert DuplicateStep(); 216 217 // Set the parent claim as countered. We do not need to append a new claim to the game; 218 // instead, we can just set the existing parent as countered. 219 parent.counteredBy = msg.sender; 220 } 221 222 /// @notice Generic move function, used for both `attack` and `defend` moves. 223 /// @param _challengeIndex The index of the claim being moved against. 224 /// @param _claim The claim at the next logical position in the game. 225 /// @param _isAttack Whether or not the move is an attack or defense. 226 function move(uint256 _challengeIndex, Claim _claim, bool _isAttack) public payable virtual { 227 // INVARIANT: Moves cannot be made unless the game is currently in progress. 228 if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); 229 230 // Get the parent. If it does not exist, the call will revert with OOB. 231 ClaimData memory parent = claimData[_challengeIndex]; 232 233 // Compute the position that the claim commits to. Because the parent's position is already 234 // known, we can compute the next position by moving left or right depending on whether 235 // or not the move is an attack or defense. 236 Position parentPos = parent.position; 237 Position nextPosition = parentPos.move(_isAttack); 238 uint256 nextPositionDepth = nextPosition.depth(); 239 240 // INVARIANT: A defense can never be made against the root claim of either the output root game or any 241 // of the execution trace bisection subgames. This is because the root claim commits to the 242 // entire state. Therefore, the only valid defense is to do nothing if it is agreed with. 243 if ((_challengeIndex == 0 || nextPositionDepth == SPLIT_DEPTH + 2) && !_isAttack) { 244 revert CannotDefendRootClaim(); 245 } 246 247 // INVARIANT: A move can never surpass the `MAX_GAME_DEPTH`. The only option to counter a 248 // claim at this depth is to perform a single instruction step on-chain via 249 // the `step` function to prove that the state transition produces an unexpected 250 // post-state. 251 if (nextPositionDepth > MAX_GAME_DEPTH) revert GameDepthExceeded(); 252 253 // When the next position surpasses the split depth (i.e., it is the root claim of an execution 254 // trace bisection sub-game), we need to perform some extra verification steps. 255 if (nextPositionDepth == SPLIT_DEPTH + 1) { 256 _verifyExecBisectionRoot(_claim, _challengeIndex, parentPos, _isAttack); 257 } 258 259 // INVARIANT: The `msg.value` must be sufficient to cover the required bond. 260 if (getRequiredBond(nextPosition) > msg.value) revert InsufficientBond(); 261 262 // Fetch the grandparent clock, if it exists. 263 // The grandparent clock should always exist unless the parent is the root claim. 264 Clock grandparentClock; 265 if (parent.parentIndex != type(uint32).max) { 266 grandparentClock = claimData[parent.parentIndex].clock; 267 } 268 269 // Compute the duration of the next clock. This is done by adding the duration of the 270 // grandparent claim to the difference between the current block timestamp and the 271 // parent's clock timestamp. 272 Duration nextDuration = Duration.wrap( 273 uint64( 274 // First, fetch the duration of the grandparent claim. 275 grandparentClock.duration().raw() 276 // Second, add the difference between the current block timestamp and the 277 // parent's clock timestamp. 278 + block.timestamp - parent.clock.timestamp().raw() 279 ) 280 ); 281 282 // INVARIANT: A move can never be made once its clock has exceeded `GAME_DURATION / 2` 283 // seconds of time. 284 if (nextDuration.raw() > GAME_DURATION.raw() >> 1) revert ClockTimeExceeded(); 285 286 // Construct the next clock with the new duration and the current block timestamp. 287 Clock nextClock = LibClock.wrap(nextDuration, Timestamp.wrap(uint64(block.timestamp))); 288 289 // INVARIANT: There cannot be multiple identical claims with identical moves on the same challengeIndex. Multiple 290 // claims at the same position may dispute the same challengeIndex. However, they must have different 291 // values. 292 ClaimHash claimHash = _claim.hashClaimPos(nextPosition, _challengeIndex); 293 if (claims[claimHash]) revert ClaimAlreadyExists(); 294 claims[claimHash] = true; 295 296 // Create the new claim. 297 claimData.push( 298 ClaimData({ 299 parentIndex: uint32(_challengeIndex), 300 // This is updated during subgame resolution 301 counteredBy: address(0), 302 claimant: msg.sender, 303 bond: uint128(msg.value), 304 claim: _claim, 305 position: nextPosition, 306 clock: nextClock 307 }) 308 ); 309 310 // Update the subgame rooted at the parent claim. 311 subgames[_challengeIndex].push(claimData.length - 1); 312 313 // Deposit the bond. 314 WETH.deposit{ value: msg.value }(); 315 316 // Emit the appropriate event for the attack or defense. 317 emit Move(_challengeIndex, _claim, msg.sender); 318 } 319 320 /// @inheritdoc IFaultDisputeGame 321 function attack(uint256 _parentIndex, Claim _claim) external payable { 322 move(_parentIndex, _claim, true); 323 } 324 325 /// @inheritdoc IFaultDisputeGame 326 function defend(uint256 _parentIndex, Claim _claim) external payable { 327 move(_parentIndex, _claim, false); 328 } 329 330 /// @inheritdoc IFaultDisputeGame 331 function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external { 332 // INVARIANT: Local data can only be added if the game is currently in progress. 333 if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); 334 335 (Claim starting, Position startingPos, Claim disputed, Position disputedPos) = 336 _findStartingAndDisputedOutputs(_execLeafIdx); 337 Hash uuid = _computeLocalContext(starting, startingPos, disputed, disputedPos); 338 339 IPreimageOracle oracle = VM.oracle(); 340 if (_ident == LocalPreimageKey.L1_HEAD_HASH) { 341 // Load the L1 head hash 342 oracle.loadLocalData(_ident, uuid.raw(), l1Head().raw(), 32, _partOffset); 343 } else if (_ident == LocalPreimageKey.STARTING_OUTPUT_ROOT) { 344 // Load the starting proposal's output root. 345 oracle.loadLocalData(_ident, uuid.raw(), starting.raw(), 32, _partOffset); 346 } else if (_ident == LocalPreimageKey.DISPUTED_OUTPUT_ROOT) { 347 // Load the disputed proposal's output root 348 oracle.loadLocalData(_ident, uuid.raw(), disputed.raw(), 32, _partOffset); 349 } else if (_ident == LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER) { 350 // Load the disputed proposal's L2 block number as a big-endian uint64 in the 351 // high order 8 bytes of the word. 352 353 // We add the index at depth + 1 to the starting block number to get the disputed L2 354 // block number. 355 uint256 l2Number = startingOutputRoot.l2BlockNumber + disputedPos.traceIndex(SPLIT_DEPTH) + 1; 356 357 oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2Number << 0xC0), 8, _partOffset); 358 } else if (_ident == LocalPreimageKey.CHAIN_ID) { 359 // Load the chain ID as a big-endian uint64 in the high order 8 bytes of the word. 360 oracle.loadLocalData(_ident, uuid.raw(), bytes32(L2_CHAIN_ID << 0xC0), 8, _partOffset); 361 } else { 362 revert InvalidLocalIdent(); 363 } 364 } 365 366 /// @inheritdoc IFaultDisputeGame 367 function l1Head() public pure returns (Hash l1Head_) { 368 l1Head_ = Hash.wrap(_getArgFixedBytes(0x20)); 369 } 370 371 /// @inheritdoc IFaultDisputeGame 372 function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) { 373 l2BlockNumber_ = _getArgUint256(0x40); 374 } 375 376 //////////////////////////////////////////////////////////////// 377 // `IDisputeGame` impl // 378 //////////////////////////////////////////////////////////////// 379 380 /// @inheritdoc IDisputeGame 381 function gameType() public view override returns (GameType gameType_) { 382 gameType_ = GAME_TYPE; 383 } 384 385 /// @inheritdoc IDisputeGame 386 function resolve() external returns (GameStatus status_) { 387 // INVARIANT: Resolution cannot occur unless the game is currently in progress. 388 if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); 389 390 // INVARIANT: Resolution cannot occur unless the absolute root subgame has been resolved. 391 if (!subgameAtRootResolved) revert OutOfOrderResolution(); 392 393 // Update the global game status; The dispute has concluded. 394 status_ = claimData[0].counteredBy == address(0) ? GameStatus.DEFENDER_WINS : GameStatus.CHALLENGER_WINS; 395 resolvedAt = Timestamp.wrap(uint64(block.timestamp)); 396 397 // Update the status and emit the resolved event, note that we're performing an assignment here. 398 emit Resolved(status = status_); 399 400 // Try to update the anchor state, this should not revert. 401 ANCHOR_STATE_REGISTRY.tryUpdateAnchorState(); 402 } 403 404 /// @inheritdoc IFaultDisputeGame 405 function resolveClaim(uint256 _claimIndex) external payable { 406 // INVARIANT: Resolution cannot occur unless the game is currently in progress. 407 if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); 408 409 ClaimData storage parent = claimData[_claimIndex]; 410 411 // INVARIANT: Cannot resolve a subgame unless the clock of its root has expired 412 uint64 parentClockDuration = parent.clock.duration().raw(); 413 uint64 timeSinceParentMove = uint64(block.timestamp) - parent.clock.timestamp().raw(); 414 if (parentClockDuration + timeSinceParentMove <= GAME_DURATION.raw() >> 1) { 415 revert ClockNotExpired(); 416 } 417 418 uint256[] storage challengeIndices = subgames[_claimIndex]; 419 uint256 challengeIndicesLen = challengeIndices.length; 420 421 // INVARIANT: Cannot resolve subgames twice 422 if (_claimIndex == 0 && subgameAtRootResolved) { 423 revert ClaimAlreadyResolved(); 424 } 425 426 // Uncontested claims are resolved implicitly unless they are the root claim. Pay out the bond to the claimant 427 // and return early. 428 if (challengeIndicesLen == 0 && _claimIndex != 0) { 429 // In the event that the parent claim is at the max depth, there will always be 0 subgames. If the 430 // `counteredBy` field is set and there are no subgames, this implies that the parent claim was successfully 431 // stepped against. In this case, we pay out the bond to the party that stepped against the parent claim. 432 // Otherwise, the parent claim is uncontested, and the bond is returned to the claimant. 433 address counteredBy = parent.counteredBy; 434 address recipient = counteredBy == address(0) ? parent.claimant : counteredBy; 435 _distributeBond(recipient, parent); 436 return; 437 } 438 439 // Assume parent is honest until proven otherwise 440 address countered = address(0); 441 Position leftmostCounter = Position.wrap(type(uint128).max); 442 for (uint256 i = 0; i < challengeIndicesLen; ++i) { 443 uint256 challengeIndex = challengeIndices[i]; 444 445 // INVARIANT: Cannot resolve a subgame containing an unresolved claim 446 if (subgames[challengeIndex].length != 0) revert OutOfOrderResolution(); 447 448 ClaimData storage claim = claimData[challengeIndex]; 449 450 // If the child subgame is uncountered and further left than the current left-most counter, 451 // update the parent subgame's `countered` address and the current `leftmostCounter`. 452 // The left-most correct counter is preferred in bond payouts in order to discourage attackers 453 // from countering invalid subgame roots via an invalid defense position. As such positions 454 // cannot be correctly countered. 455 // Note that correctly positioned defense, but invalid claimes can still be successfully countered. 456 if (claim.counteredBy == address(0) && leftmostCounter.raw() > claim.position.raw()) { 457 countered = claim.claimant; 458 leftmostCounter = claim.position; 459 } 460 } 461 462 // If the parent was not successfully countered, pay out the parent's bond to the claimant. 463 // If the parent was successfully countered, pay out the parent's bond to the challenger. 464 _distributeBond(countered == address(0) ? parent.claimant : countered, parent); 465 466 // Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to 467 // resolveClaim will not need to traverse this subgame. 468 parent.counteredBy = countered; 469 470 // Resolved subgames have no entries 471 delete subgames[_claimIndex]; 472 473 // Indicate the game is ready to be resolved globally. 474 if (_claimIndex == 0) { 475 subgameAtRootResolved = true; 476 } 477 } 478 479 /// @inheritdoc IDisputeGame 480 function rootClaim() public pure returns (Claim rootClaim_) { 481 rootClaim_ = Claim.wrap(_getArgFixedBytes(0x00)); 482 } 483 484 /// @inheritdoc IDisputeGame 485 function extraData() public pure returns (bytes memory extraData_) { 486 // The extra data starts at the second word within the cwia calldata and 487 // is 32 bytes long. 488 extraData_ = _getArgDynBytes(0x40, 0x20); 489 } 490 491 /// @inheritdoc IDisputeGame 492 function gameData() external view returns (GameType gameType_, Claim rootClaim_, bytes memory extraData_) { 493 gameType_ = gameType(); 494 rootClaim_ = rootClaim(); 495 extraData_ = extraData(); 496 } 497 498 /// @inheritdoc IFaultDisputeGame 499 function startingBlockNumber() external view returns (uint256 startingBlockNumber_) { 500 startingBlockNumber_ = startingOutputRoot.l2BlockNumber; 501 } 502 503 /// @inheritdoc IFaultDisputeGame 504 function startingRootHash() external view returns (Hash startingRootHash_) { 505 startingRootHash_ = startingOutputRoot.root; 506 } 507 508 //////////////////////////////////////////////////////////////// 509 // MISC EXTERNAL // 510 //////////////////////////////////////////////////////////////// 511 512 /// @inheritdoc IInitializable 513 function initialize() public payable virtual { 514 // SAFETY: Any revert in this function will bubble up to the DisputeGameFactory and 515 // prevent the game from being created. 516 // 517 // Implicit assumptions: 518 // - The `gameStatus` state variable defaults to 0, which is `GameStatus.IN_PROGRESS` 519 // - The dispute game factory will enforce the required bond to initialize the game. 520 // 521 // Explicit checks: 522 // - The game must not have already been initialized. 523 // - An output root cannot be proposed at or before the starting block number. 524 525 // INVARIANT: The game must not have already been initialized. 526 if (initialized) revert AlreadyInitialized(); 527 528 // Grab the latest anchor root. 529 (Hash root, uint256 rootBlockNumber) = ANCHOR_STATE_REGISTRY.anchors(GAME_TYPE); 530 531 // Should only happen if this is a new game type that hasn't been set up yet. 532 if (root.raw() == bytes32(0)) revert AnchorRootNotFound(); 533 534 // Set the starting output root. 535 startingOutputRoot = OutputRoot({ l2BlockNumber: rootBlockNumber, root: root }); 536 537 // Do not allow the game to be initialized if the root claim corresponds to a block at or before the 538 // configured starting block number. 539 if (l2BlockNumber() <= rootBlockNumber) revert UnexpectedRootClaim(rootClaim()); 540 541 // Revert if the calldata size is too large, which signals that the `extraData` contains more than expected. 542 // This is to prevent adding extra bytes to the `extraData` that result in a different game UUID in the factory, 543 // but are not used by the game, which would allow for multiple dispute games for the same output proposal to 544 // be created. 545 // Expected length: 0x66 (0x04 selector + 0x20 root claim + 0x20 l1 head + 0x20 extraData + 0x02 CWIA bytes) 546 assembly { 547 if gt(calldatasize(), 0x66) { 548 // Store the selector for `ExtraDataTooLong()` & revert 549 mstore(0x00, 0xc407e025) 550 revert(0x1C, 0x04) 551 } 552 } 553 554 // Set the root claim 555 claimData.push( 556 ClaimData({ 557 parentIndex: type(uint32).max, 558 counteredBy: address(0), 559 claimant: tx.origin, 560 bond: uint128(msg.value), 561 claim: rootClaim(), 562 position: ROOT_POSITION, 563 clock: LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))) 564 }) 565 ); 566 567 // Deposit the bond. 568 WETH.deposit{ value: msg.value }(); 569 570 // Set the game's starting timestamp 571 createdAt = Timestamp.wrap(uint64(block.timestamp)); 572 573 // Set the game as initialized. 574 initialized = true; 575 } 576 577 /// @notice Returns the length of the `claimData` array. 578 function claimDataLen() external view returns (uint256 len_) { 579 len_ = claimData.length; 580 } 581 582 /// @notice Returns the required bond for a given move kind. 583 /// @param _position The position of the bonded interaction. 584 /// @return requiredBond_ The required ETH bond for the given move, in wei. 585 function getRequiredBond(Position _position) public view returns (uint256 requiredBond_) { 586 uint256 depth = uint256(_position.depth()); 587 if (depth > MAX_GAME_DEPTH) revert GameDepthExceeded(); 588 589 // Values taken from Big Bonds v1.5 (TM) spec. 590 uint256 assumedBaseFee = 200 gwei; 591 uint256 baseGasCharged = 400_000; 592 uint256 highGasCharged = 200_000_000; 593 594 // Goal here is to compute the fixed multiplier that will be applied to the base gas 595 // charged to get the required gas amount for the given depth. We apply this multiplier 596 // some `n` times where `n` is the depth of the position. We are looking for some number 597 // that, when multiplied by itself `MAX_GAME_DEPTH` times and then multiplied by the base 598 // gas charged, will give us the maximum gas that we want to charge. 599 // We want to solve for (highGasCharged/baseGasCharged) ** (1/MAX_GAME_DEPTH). 600 // We know that a ** (b/c) is equal to e ** (ln(a) * (b/c)). 601 // We can compute e ** (ln(a) * (b/c)) quite easily with FixedPointMathLib. 602 603 // Set up a, b, and c. 604 uint256 a = highGasCharged / baseGasCharged; 605 uint256 b = FixedPointMathLib.WAD; 606 uint256 c = MAX_GAME_DEPTH * FixedPointMathLib.WAD; 607 608 // Compute ln(a). 609 // slither-disable-next-line divide-before-multiply 610 uint256 lnA = uint256(FixedPointMathLib.lnWad(int256(a * FixedPointMathLib.WAD))); 611 612 // Computes (b / c) with full precision using WAD = 1e18. 613 uint256 bOverC = FixedPointMathLib.divWad(b, c); 614 615 // Compute e ** (ln(a) * (b/c)) 616 // sMulWad can be used here since WAD = 1e18 maintains the same precision. 617 uint256 numerator = FixedPointMathLib.mulWad(lnA, bOverC); 618 int256 base = FixedPointMathLib.expWad(int256(numerator)); 619 620 // Compute the required gas amount. 621 int256 rawGas = FixedPointMathLib.powWad(base, int256(depth * FixedPointMathLib.WAD)); 622 uint256 requiredGas = FixedPointMathLib.mulWad(baseGasCharged, uint256(rawGas)); 623 624 // Compute the required bond. 625 requiredBond_ = assumedBaseFee * requiredGas; 626 } 627 628 /// @notice Claim the credit belonging to the recipient address. 629 /// @param _recipient The owner and recipient of the credit. 630 function claimCredit(address _recipient) external { 631 // Remove the credit from the recipient prior to performing the external call. 632 uint256 recipientCredit = credit[_recipient]; 633 credit[_recipient] = 0; 634 635 // Revert if the recipient has no credit to claim. 636 if (recipientCredit == 0) { 637 revert NoCreditToClaim(); 638 } 639 640 // Try to withdraw the WETH amount so it can be used here. 641 WETH.withdraw(_recipient, recipientCredit); 642 643 // Transfer the credit to the recipient. 644 (bool success,) = _recipient.call{ value: recipientCredit }(hex""); 645 if (!success) revert BondTransferFailed(); 646 } 647 648 /// @notice Returns the flag set in the `bond` field of a `ClaimData` struct to indicate that the bond has been 649 /// claimed. 650 function claimedBondFlag() external pure returns (uint128 claimedBondFlag_) { 651 claimedBondFlag_ = CLAIMED_BOND_FLAG; 652 } 653 654 //////////////////////////////////////////////////////////////// 655 // IMMUTABLE GETTERS // 656 //////////////////////////////////////////////////////////////// 657 658 /// @notice Returns the absolute prestate of the instruction trace. 659 function absolutePrestate() external view returns (Claim absolutePrestate_) { 660 absolutePrestate_ = ABSOLUTE_PRESTATE; 661 } 662 663 /// @notice Returns the max game depth. 664 function maxGameDepth() external view returns (uint256 maxGameDepth_) { 665 maxGameDepth_ = MAX_GAME_DEPTH; 666 } 667 668 /// @notice Returns the split depth. 669 function splitDepth() external view returns (uint256 splitDepth_) { 670 splitDepth_ = SPLIT_DEPTH; 671 } 672 673 /// @notice Returns the game duration. 674 function gameDuration() external view returns (Duration gameDuration_) { 675 gameDuration_ = GAME_DURATION; 676 } 677 678 /// @notice Returns the address of the VM. 679 function vm() external view returns (IBigStepper vm_) { 680 vm_ = VM; 681 } 682 683 /// @notice Returns the WETH contract for holding ETH. 684 function weth() external view returns (IDelayedWETH weth_) { 685 weth_ = WETH; 686 } 687 688 /// @notice Returns the chain ID of the L2 network this contract argues about. 689 function l2ChainId() external view returns (uint256 l2ChainId_) { 690 l2ChainId_ = L2_CHAIN_ID; 691 } 692 693 //////////////////////////////////////////////////////////////// 694 // HELPERS // 695 //////////////////////////////////////////////////////////////// 696 697 /// @notice Pays out the bond of a claim to a given recipient. 698 /// @param _recipient The recipient of the bond. 699 /// @param _bonded The claim to pay out the bond of. 700 function _distributeBond(address _recipient, ClaimData storage _bonded) internal { 701 // Set all bits in the bond value to indicate that the bond has been paid out. 702 uint256 bond = _bonded.bond; 703 if (bond == CLAIMED_BOND_FLAG) revert ClaimAlreadyResolved(); 704 _bonded.bond = CLAIMED_BOND_FLAG; 705 706 // Increase the recipient's credit. 707 credit[_recipient] += bond; 708 709 // Unlock the bond. 710 WETH.unlock(_recipient, bond); 711 } 712 713 /// @notice Verifies the integrity of an execution bisection subgame's root claim. Reverts if the claim 714 /// is invalid. 715 /// @param _rootClaim The root claim of the execution bisection subgame. 716 function _verifyExecBisectionRoot( 717 Claim _rootClaim, 718 uint256 _parentIdx, 719 Position _parentPos, 720 bool _isAttack 721 ) 722 internal 723 view 724 { 725 // The root claim of an execution trace bisection sub-game must: 726 // 1. Signal that the VM panicked or resulted in an invalid transition if the disputed output root 727 // was made by the opposing party. 728 // 2. Signal that the VM resulted in a valid transition if the disputed output root was made by the same party. 729 730 // If the move is a defense, the disputed output could have been made by either party. In this case, we 731 // need to search for the parent output to determine what the expected status byte should be. 732 Position disputedLeafPos = Position.wrap(_parentPos.raw() + 1); 733 ClaimData storage disputed = _findTraceAncestor({ _pos: disputedLeafPos, _start: _parentIdx, _global: true }); 734 uint8 vmStatus = uint8(_rootClaim.raw()[0]); 735 736 if (_isAttack || disputed.position.depth() % 2 == SPLIT_DEPTH % 2) { 737 // If the move is an attack, the parent output is always deemed to be disputed. In this case, we only need 738 // to check that the root claim signals that the VM panicked or resulted in an invalid transition. 739 // If the move is a defense, and the disputed output and creator of the execution trace subgame disagree, 740 // the root claim should also signal that the VM panicked or resulted in an invalid transition. 741 if (!(vmStatus == VMStatuses.INVALID.raw() || vmStatus == VMStatuses.PANIC.raw())) { 742 revert UnexpectedRootClaim(_rootClaim); 743 } 744 } else if (vmStatus != VMStatuses.VALID.raw()) { 745 // The disputed output and the creator of the execution trace subgame agree. The status byte should 746 // have signaled that the VM succeeded. 747 revert UnexpectedRootClaim(_rootClaim); 748 } 749 } 750 751 /// @notice Finds the trace ancestor of a given position within the DAG. 752 /// @param _pos The position to find the trace ancestor claim of. 753 /// @param _start The index to start searching from. 754 /// @param _global Whether or not to search the entire dag or just within an execution trace subgame. If set to 755 /// `true`, and `_pos` is at or above the split depth, this function will revert. 756 /// @return ancestor_ The ancestor claim that commits to the same trace index as `_pos`. 757 function _findTraceAncestor( 758 Position _pos, 759 uint256 _start, 760 bool _global 761 ) 762 internal 763 view 764 returns (ClaimData storage ancestor_) 765 { 766 // Grab the trace ancestor's expected position. 767 Position traceAncestorPos = _global ? _pos.traceAncestor() : _pos.traceAncestorBounded(SPLIT_DEPTH); 768 769 // Walk up the DAG to find a claim that commits to the same trace index as `_pos`. It is 770 // guaranteed that such a claim exists. 771 ancestor_ = claimData[_start]; 772 while (ancestor_.position.raw() != traceAncestorPos.raw()) { 773 ancestor_ = claimData[ancestor_.parentIndex]; 774 } 775 } 776 777 /// @notice Finds the starting and disputed output root for a given `ClaimData` within the DAG. This 778 /// `ClaimData` must be below the `SPLIT_DEPTH`. 779 /// @param _start The index within `claimData` of the claim to start searching from. 780 /// @return startingClaim_ The starting output root claim. 781 /// @return startingPos_ The starting output root position. 782 /// @return disputedClaim_ The disputed output root claim. 783 /// @return disputedPos_ The disputed output root position. 784 function _findStartingAndDisputedOutputs(uint256 _start) 785 internal 786 view 787 returns (Claim startingClaim_, Position startingPos_, Claim disputedClaim_, Position disputedPos_) 788 { 789 // Fatch the starting claim. 790 uint256 claimIdx = _start; 791 ClaimData storage claim = claimData[claimIdx]; 792 793 // If the starting claim's depth is less than or equal to the split depth, we revert as this is UB. 794 if (claim.position.depth() <= SPLIT_DEPTH) revert ClaimAboveSplit(); 795 796 // We want to: 797 // 1. Find the first claim at the split depth. 798 // 2. Determine whether it was the starting or disputed output for the exec game. 799 // 3. Find the complimentary claim depending on the info from #2 (pre or post). 800 801 // Walk up the DAG until the ancestor's depth is equal to the split depth. 802 uint256 currentDepth; 803 ClaimData storage execRootClaim = claim; 804 while ((currentDepth = claim.position.depth()) > SPLIT_DEPTH) { 805 uint256 parentIndex = claim.parentIndex; 806 807 // If we're currently at the split depth + 1, we're at the root of the execution sub-game. 808 // We need to keep track of the root claim here to determine whether the execution sub-game was 809 // started with an attack or defense against the output leaf claim. 810 if (currentDepth == SPLIT_DEPTH + 1) execRootClaim = claim; 811 812 claim = claimData[parentIndex]; 813 claimIdx = parentIndex; 814 } 815 816 // Determine whether the start of the execution sub-game was an attack or defense to the output root 817 // above. This is important because it determines which claim is the starting output root and which 818 // is the disputed output root. 819 (Position execRootPos, Position outputPos) = (execRootClaim.position, claim.position); 820 bool wasAttack = execRootPos.parent().raw() == outputPos.raw(); 821 822 // Determine the starting and disputed output root indices. 823 // 1. If it was an attack, the disputed output root is `claim`, and the starting output root is 824 // elsewhere in the DAG (it must commit to the block # index at depth of `outputPos - 1`). 825 // 2. If it was a defense, the starting output root is `claim`, and the disputed output root is 826 // elsewhere in the DAG (it must commit to the block # index at depth of `outputPos + 1`). 827 if (wasAttack) { 828 // If this is an attack on the first output root (the block directly after the starting 829 // block number), the starting claim nor position exists in the tree. We leave these as 830 // 0, which can be easily identified due to 0 being an invalid Gindex. 831 if (outputPos.indexAtDepth() > 0) { 832 ClaimData storage starting = _findTraceAncestor(Position.wrap(outputPos.raw() - 1), claimIdx, true); 833 (startingClaim_, startingPos_) = (starting.claim, starting.position); 834 } else { 835 startingClaim_ = Claim.wrap(startingOutputRoot.root.raw()); 836 } 837 (disputedClaim_, disputedPos_) = (claim.claim, claim.position); 838 } else { 839 ClaimData storage disputed = _findTraceAncestor(Position.wrap(outputPos.raw() + 1), claimIdx, true); 840 (startingClaim_, startingPos_) = (claim.claim, claim.position); 841 (disputedClaim_, disputedPos_) = (disputed.claim, disputed.position); 842 } 843 } 844 845 /// @notice Finds the local context hash for a given claim index that is present in an execution trace subgame. 846 /// @param _claimIndex The index of the claim to find the local context hash for. 847 /// @return uuid_ The local context hash. 848 function _findLocalContext(uint256 _claimIndex) internal view returns (Hash uuid_) { 849 (Claim starting, Position startingPos, Claim disputed, Position disputedPos) = 850 _findStartingAndDisputedOutputs(_claimIndex); 851 uuid_ = _computeLocalContext(starting, startingPos, disputed, disputedPos); 852 } 853 854 /// @notice Computes the local context hash for a set of starting/disputed claim values and positions. 855 /// @param _starting The starting claim. 856 /// @param _startingPos The starting claim's position. 857 /// @param _disputed The disputed claim. 858 /// @param _disputedPos The disputed claim's position. 859 /// @return uuid_ The local context hash. 860 function _computeLocalContext( 861 Claim _starting, 862 Position _startingPos, 863 Claim _disputed, 864 Position _disputedPos 865 ) 866 internal 867 pure 868 returns (Hash uuid_) 869 { 870 // A position of 0 indicates that the starting claim is the absolute prestate. In this special case, 871 // we do not include the starting claim within the local context hash. 872 if (_startingPos.raw() == 0) { 873 uuid_ = Hash.wrap(keccak256(abi.encode(_disputed, _disputedPos))); 874 } else { 875 uuid_ = Hash.wrap(keccak256(abi.encode(_starting, _startingPos, _disputed, _disputedPos))); 876 } 877 } 878 }