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

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Test } from "forge-std/Test.sol";
     5  import { StdUtils } from "forge-std/StdUtils.sol";
     6  import { StdCheats } from "forge-std/StdCheats.sol";
     7  import { Safe, OwnerManager } from "safe-contracts/Safe.sol";
     8  import { SafeProxyFactory } from "safe-contracts/proxies/SafeProxyFactory.sol";
     9  import { ModuleManager } from "safe-contracts/base/ModuleManager.sol";
    10  import { Enum } from "safe-contracts/common/Enum.sol";
    11  import "test/safe-tools/SafeTestTools.sol";
    12  import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    13  
    14  import { LivenessGuard } from "src/Safe/LivenessGuard.sol";
    15  
    16  /// @dev A wrapper contract exposing the length of the ownersBefore set in the LivenessGuard.
    17  contract WrappedGuard is LivenessGuard {
    18      using EnumerableSet for EnumerableSet.AddressSet;
    19  
    20      constructor(Safe safe) LivenessGuard(safe) { }
    21  
    22      function ownersBeforeLength() public view returns (uint256) {
    23          return ownersBefore.length();
    24      }
    25  }
    26  
    27  contract LivenessGuard_TestInit is Test, SafeTestTools {
    28      using SafeTestLib for SafeInstance;
    29  
    30      event OwnerRecorded(address owner);
    31  
    32      uint256 initTime = 10;
    33      WrappedGuard livenessGuard;
    34      SafeInstance safeInstance;
    35  
    36      /// @dev Sets up the test environment
    37      function setUp() public {
    38          vm.warp(initTime);
    39          safeInstance = _setupSafe();
    40          livenessGuard = new WrappedGuard(safeInstance.safe);
    41          safeInstance.setGuard(address(livenessGuard));
    42      }
    43  }
    44  
    45  contract LivenessGuard_Constructor_Test is LivenessGuard_TestInit {
    46      /// @dev Tests that the constructor correctly sets the current time as the lastLive time for each owner
    47      function test_constructor_works() external {
    48          address[] memory owners = safeInstance.owners;
    49          livenessGuard = new WrappedGuard(safeInstance.safe);
    50          for (uint256 i; i < owners.length; i++) {
    51              assertEq(livenessGuard.lastLive(owners[i]), initTime);
    52          }
    53      }
    54  }
    55  
    56  contract LivenessGuard_Getters_Test is LivenessGuard_TestInit {
    57      /// @dev Tests that the getters return the correct values
    58      function test_getters_works() external {
    59          assertEq(address(livenessGuard.safe()), address(safeInstance.safe));
    60          assertEq(livenessGuard.lastLive(address(0)), 0);
    61      }
    62  }
    63  
    64  contract LivenessGuard_CheckTx_TestFails is LivenessGuard_TestInit {
    65      /// @dev Tests that the checkTransaction function reverts if the caller is not the Safe
    66      function test_checkTransaction_callerIsNotSafe_revert() external {
    67          vm.expectRevert("LivenessGuard: only Safe can call this function");
    68          livenessGuard.checkTransaction({
    69              to: address(0),
    70              value: 0,
    71              data: hex"00",
    72              operation: Enum.Operation.Call,
    73              safeTxGas: 0,
    74              baseGas: 0,
    75              gasPrice: 0,
    76              gasToken: address(0),
    77              refundReceiver: payable(address(0)),
    78              signatures: hex"00",
    79              msgSender: address(0)
    80          });
    81      }
    82  }
    83  
    84  contract LivenessGuard_CheckTx_Test is LivenessGuard_TestInit {
    85      using SafeTestLib for SafeInstance;
    86  
    87      /// @dev Tests that the checkTransaction function succeeds
    88      function test_checkTransaction_succeeds() external {
    89          // Create an array of the addresses who will sign the transaction. SafeTestTools
    90          // will generate these signatures up to the threshold by iterating over the owners array.
    91          address[] memory signers = new address[](safeInstance.threshold);
    92          signers[0] = safeInstance.owners[0];
    93          signers[1] = safeInstance.owners[1];
    94  
    95          // Record the timestamps before the transaction
    96          uint256[] memory beforeTimestamps = new uint256[](safeInstance.owners.length);
    97  
    98          // Jump ahead
    99          uint256 newTimestamp = block.timestamp + 100;
   100          vm.warp(newTimestamp);
   101  
   102          for (uint256 i; i < signers.length; i++) {
   103              vm.expectEmit(address(livenessGuard));
   104              emit OwnerRecorded(signers[i]);
   105          }
   106          vm.expectCall(address(safeInstance.safe), abi.encodeWithSignature("nonce()"));
   107          vm.expectCall(address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ()));
   108          safeInstance.execTransaction({ to: address(1111), value: 0, data: hex"abba" });
   109          for (uint256 i; i < safeInstance.threshold; i++) {
   110              uint256 lastLive = livenessGuard.lastLive(safeInstance.owners[i]);
   111              assertGe(lastLive, beforeTimestamps[i]);
   112              assertEq(lastLive, newTimestamp);
   113          }
   114      }
   115  }
   116  
   117  contract LivenessGuard_CheckAfterExecution_TestFails is LivenessGuard_TestInit {
   118      /// @dev Tests that the checkAfterExecution function reverts if the caller is not the Safe
   119      function test_checkAfterExecution_callerIsNotSafe_revert() external {
   120          vm.expectRevert("LivenessGuard: only Safe can call this function");
   121          livenessGuard.checkAfterExecution(bytes32(0), false);
   122      }
   123  }
   124  
   125  contract LivenessGuard_ShowLiveness_TestFail is LivenessGuard_TestInit {
   126      /// @dev Tests that the showLiveness function reverts if the caller is not an owner
   127      function test_showLiveness_callIsNotSafeOwner_reverts() external {
   128          vm.expectRevert("LivenessGuard: only Safe owners may demonstrate liveness");
   129          livenessGuard.showLiveness();
   130      }
   131  }
   132  
   133  contract LivenessGuard_ShowLiveness_Test is LivenessGuard_TestInit {
   134      /// @dev Tests that the showLiveness function succeeds
   135      function test_showLiveness_succeeds() external {
   136          // Cache the caller
   137          address caller = safeInstance.owners[0];
   138  
   139          vm.expectEmit(address(livenessGuard));
   140          emit OwnerRecorded(caller);
   141  
   142          vm.prank(caller);
   143          livenessGuard.showLiveness();
   144  
   145          assertEq(livenessGuard.lastLive(caller), block.timestamp);
   146      }
   147  }
   148  
   149  contract LivenessGuard_OwnerManagement_Test is LivenessGuard_TestInit {
   150      using SafeTestLib for SafeInstance;
   151  
   152      /// @dev Tests that the guard correctly deletes the owner from the lastLive mapping when it is removed
   153      function test_removeOwner_succeeds() external {
   154          address ownerToRemove = safeInstance.owners[0];
   155          assertGe(livenessGuard.lastLive(ownerToRemove), 0);
   156          assertTrue(safeInstance.safe.isOwner(ownerToRemove));
   157  
   158          assertEq(livenessGuard.ownersBeforeLength(), 0);
   159          safeInstance.removeOwner({ prevOwner: address(0), owner: ownerToRemove, threshold: 1 });
   160          assertEq(livenessGuard.ownersBeforeLength(), 0);
   161  
   162          assertFalse(safeInstance.safe.isOwner(ownerToRemove));
   163          assertEq(livenessGuard.lastLive(ownerToRemove), 0);
   164      }
   165  
   166      /// @dev Tests that the guard correctly adds an owner to the lastLive mapping when it is added
   167      function test_addOwner_succeeds() external {
   168          address ownerToAdd = makeAddr("new owner");
   169          assertEq(livenessGuard.lastLive(ownerToAdd), 0);
   170          assertFalse(safeInstance.safe.isOwner(ownerToAdd));
   171  
   172          assertEq(livenessGuard.ownersBeforeLength(), 0);
   173          safeInstance.addOwnerWithThreshold({ owner: ownerToAdd, threshold: 1 });
   174          assertEq(livenessGuard.ownersBeforeLength(), 0);
   175  
   176          assertTrue(safeInstance.safe.isOwner(ownerToAdd));
   177          assertEq(livenessGuard.lastLive(ownerToAdd), block.timestamp);
   178      }
   179  
   180      /// @dev Tests that the guard correctly adds an owner to the lastLive mapping when it is added
   181      function test_swapOwner_succeeds() external {
   182          address ownerToRemove = safeInstance.owners[0];
   183          assertGe(livenessGuard.lastLive(ownerToRemove), 0);
   184          assertTrue(safeInstance.safe.isOwner(ownerToRemove));
   185  
   186          address ownerToAdd = makeAddr("new owner");
   187          assertEq(livenessGuard.lastLive(ownerToAdd), 0);
   188          assertFalse(safeInstance.safe.isOwner(ownerToAdd));
   189  
   190          assertEq(livenessGuard.ownersBeforeLength(), 0);
   191          safeInstance.swapOwner({ prevOwner: address(0), oldOwner: ownerToRemove, newOwner: ownerToAdd });
   192          assertEq(livenessGuard.ownersBeforeLength(), 0);
   193  
   194          assertFalse(safeInstance.safe.isOwner(ownerToRemove));
   195          assertEq(livenessGuard.lastLive(ownerToRemove), 0);
   196  
   197          assertTrue(safeInstance.safe.isOwner(ownerToAdd));
   198          assertEq(livenessGuard.lastLive(ownerToAdd), block.timestamp);
   199      }
   200  }
   201  
   202  contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, LivenessGuard_TestInit {
   203      using SafeTestLib for SafeInstance;
   204  
   205      /// @dev Enumerates the possible owner management operations
   206      enum OwnerOp {
   207          Add,
   208          Remove,
   209          Swap
   210      }
   211  
   212      /// @dev Describes a change to be made to the safe
   213      struct OwnerChange {
   214          uint8 timeDelta; // used to warp the vm
   215          uint8 operation; // used to choose an OwnerOp
   216          uint256 ownerIndex; // used to choose the owner to remove or swap out
   217          uint256 newThreshold;
   218      }
   219  
   220      /// @dev Maps addresses to private keys
   221      mapping(address => uint256) privateKeys;
   222  
   223      /// @dev Tests that the guard correctly manages the lastLive mapping when owners are added, removed, or swapped
   224      function testFuzz_OwnerManagement_works(
   225          uint256 initialOwners,
   226          uint256 threshold,
   227          OwnerChange[] memory changes
   228      )
   229          external
   230      {
   231          vm.assume(changes.length < 20);
   232          // Initialize the safe with more owners than changes, to ensure we don't try to remove them all
   233          initialOwners = bound(initialOwners, changes.length, 2 * changes.length);
   234  
   235          // We need at least one owner
   236          initialOwners = initialOwners < 1 ? 1 : initialOwners;
   237  
   238          // Limit the threshold to the number of owners
   239          threshold = bound(threshold, 1, initialOwners);
   240  
   241          // Generate the initial owners and keys and setup the safe
   242          (address[] memory ownerAddrs, uint256[] memory ownerkeys) =
   243              SafeTestLib.makeAddrsAndKeys("safeTest", initialOwners);
   244          // record the private keys for later use
   245          for (uint256 i; i < ownerAddrs.length; i++) {
   246              privateKeys[ownerAddrs[i]] = ownerkeys[i];
   247          }
   248  
   249          // Create the new safe and register the guard.
   250          SafeInstance memory safeInstance = _setupSafe(ownerkeys, threshold);
   251          livenessGuard = new WrappedGuard(safeInstance.safe);
   252          safeInstance.setGuard(address(livenessGuard));
   253  
   254          for (uint256 i; i < changes.length; i++) {
   255              vm.warp(block.timestamp + changes[i].timeDelta);
   256              OwnerChange memory change = changes[i];
   257              address[] memory currentOwners = safeInstance.safe.getOwners();
   258  
   259              // Create a new owner address to add and store the key
   260              (address newOwner, uint256 newKey) = makeAddrAndKey(string.concat("new owner", vm.toString(i)));
   261              privateKeys[newOwner] = newKey;
   262  
   263              OwnerOp op = OwnerOp(bound(change.operation, 0, uint256(type(OwnerOp).max)));
   264  
   265              assertEq(livenessGuard.ownersBeforeLength(), 0);
   266              if (op == OwnerOp.Add) {
   267                  assertEq(livenessGuard.lastLive(newOwner), 0);
   268                  assertFalse(safeInstance.safe.isOwner(newOwner));
   269                  change.newThreshold = bound(change.newThreshold, 1, currentOwners.length + 1);
   270  
   271                  safeInstance.addOwnerWithThreshold(newOwner, change.newThreshold);
   272  
   273                  assertTrue(safeInstance.safe.isOwner(newOwner));
   274                  assertEq(livenessGuard.lastLive(newOwner), block.timestamp);
   275              } else {
   276                  // Ensure we're removing an owner at an index within bounds
   277                  uint256 ownerIndexToRemove = bound(change.ownerIndex, 0, currentOwners.length - 1);
   278                  address ownerToRemove = currentOwners[ownerIndexToRemove];
   279                  address prevOwner = safeInstance.getPrevOwner(ownerToRemove);
   280  
   281                  if (op == OwnerOp.Remove) {
   282                      if (currentOwners.length == 1) continue;
   283                      assertGe(livenessGuard.lastLive(ownerToRemove), 0);
   284                      assertTrue(safeInstance.safe.isOwner(ownerToRemove));
   285                      change.newThreshold = bound(change.newThreshold, 1, currentOwners.length - 1);
   286  
   287                      safeInstance.removeOwner(prevOwner, ownerToRemove, change.newThreshold);
   288  
   289                      assertFalse(safeInstance.safe.isOwner(ownerToRemove));
   290                      assertEq(livenessGuard.lastLive(ownerToRemove), 0);
   291                  } else if (op == OwnerOp.Swap) {
   292                      assertGe(livenessGuard.lastLive(ownerToRemove), 0);
   293                      assertTrue(safeInstance.safe.isOwner(ownerToRemove));
   294  
   295                      safeInstance.swapOwner(prevOwner, ownerToRemove, newOwner);
   296  
   297                      assertTrue(safeInstance.safe.isOwner(newOwner));
   298                      assertFalse(safeInstance.safe.isOwner(ownerToRemove));
   299                      assertEq(livenessGuard.lastLive(ownerToRemove), 0);
   300                      assertEq(livenessGuard.lastLive(newOwner), block.timestamp);
   301                  }
   302              }
   303              assertEq(livenessGuard.ownersBeforeLength(), 0);
   304              _refreshOwners(safeInstance);
   305          }
   306      }
   307  
   308      /// @dev Refreshes the owners and ownerPKs arrays in the SafeInstance
   309      function _refreshOwners(SafeInstance memory instance) internal view {
   310          // Get the current owners
   311          instance.owners = instance.safe.getOwners();
   312  
   313          // Looks up the private key for each owner
   314          uint256[] memory unsortedOwnerPKs = new uint256[](instance.owners.length);
   315          for (uint256 i; i < instance.owners.length; i++) {
   316              unsortedOwnerPKs[i] = privateKeys[instance.owners[i]];
   317          }
   318  
   319          // Sort the keys by address and store them in the SafeInstance
   320          instance.ownerPKs = SafeTestLib.sortPKsByComputedAddress(unsortedOwnerPKs);
   321  
   322          // Overwrite the SafeInstances owners array with the computed addresses from the ownerPKs array
   323          for (uint256 i; i < instance.owners.length; i++) {
   324              instance.owners[i] = SafeTestLib.getAddr(instance.ownerPKs[i]);
   325          }
   326      }
   327  }