github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/invariants/FaultDisputeGame.t.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Vm } from "forge-std/Vm.sol";
     5  import { StdUtils } from "forge-std/StdUtils.sol";
     6  import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol";
     7  import { FaultDisputeGame_Init } from "test/dispute/FaultDisputeGame.t.sol";
     8  
     9  import "src/libraries/DisputeTypes.sol";
    10  import "src/libraries/DisputeErrors.sol";
    11  
    12  contract FaultDisputeGame_Solvency_Invariant is FaultDisputeGame_Init {
    13      Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10)));
    14      Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32((uint256(3) << 248) | uint256(0)));
    15  
    16      RandomClaimActor internal actor;
    17      uint256 internal defaultSenderBalance;
    18  
    19      function setUp() public override {
    20          super.setUp();
    21          super.init({ rootClaim: ROOT_CLAIM, absolutePrestate: ABSOLUTE_PRESTATE, l2BlockNumber: 0x10 });
    22  
    23          actor = new RandomClaimActor(gameProxy, vm);
    24  
    25          targetContract(address(actor));
    26          vm.startPrank(address(actor));
    27      }
    28  
    29      /// @custom:invariant FaultDisputeGame always returns all ETH on total resolution
    30      ///
    31      /// The FaultDisputeGame contract should always return all ETH in the contract to the correct recipients upon
    32      /// resolution of all outstanding claims. There may never be any ETH left in the contract after a full resolution.
    33      function invariant_faultDisputeGame_solvency() public {
    34          vm.warp(block.timestamp + 7 days + 1 seconds);
    35  
    36          (,,, uint256 rootBond,,,) = gameProxy.claimData(0);
    37  
    38          for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) {
    39              (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1)));
    40              assertTrue(success);
    41          }
    42          gameProxy.resolve();
    43  
    44          // Wait for the withdrawal delay.
    45          vm.warp(block.timestamp + 7 days + 1 seconds);
    46  
    47          if (gameProxy.credit(address(this)) == 0) {
    48              vm.expectRevert(NoCreditToClaim.selector);
    49              gameProxy.claimCredit(address(this));
    50          } else {
    51              gameProxy.claimCredit(address(this));
    52          }
    53  
    54          if (gameProxy.credit(address(actor)) == 0) {
    55              vm.expectRevert(NoCreditToClaim.selector);
    56              gameProxy.claimCredit(address(actor));
    57          } else {
    58              gameProxy.claimCredit(address(actor));
    59          }
    60  
    61          if (gameProxy.status() == GameStatus.DEFENDER_WINS) {
    62              assertEq(address(this).balance, type(uint96).max);
    63              assertEq(address(actor).balance, actor.totalBonded() - rootBond);
    64          } else if (gameProxy.status() == GameStatus.CHALLENGER_WINS) {
    65              assertEq(DEFAULT_SENDER.balance, type(uint96).max - rootBond);
    66              assertEq(address(actor).balance, actor.totalBonded() + rootBond);
    67          } else {
    68              revert("unreachable");
    69          }
    70  
    71          assertEq(address(gameProxy).balance, 0);
    72      }
    73  }
    74  
    75  contract RandomClaimActor is StdUtils {
    76      FaultDisputeGame internal immutable GAME;
    77      Vm internal immutable VM;
    78  
    79      uint256 public totalBonded;
    80  
    81      constructor(FaultDisputeGame _gameProxy, Vm _vm) {
    82          GAME = _gameProxy;
    83          VM = _vm;
    84      }
    85  
    86      function move(bool _isAttack, uint256 _parentIndex, Claim _claim, uint64 _bondAmount) public {
    87          _parentIndex = bound(_parentIndex, 0, GAME.claimDataLen());
    88          VM.deal(address(this), _bondAmount);
    89  
    90          totalBonded += _bondAmount;
    91  
    92          GAME.move{ value: _bondAmount }(_parentIndex, _claim, _isAttack);
    93      }
    94  
    95      fallback() external payable { }
    96  
    97      receive() external payable { }
    98  }