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

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Test, StdUtils } from "forge-std/Test.sol";
     5  import { Safe } from "safe-contracts/Safe.sol";
     6  import { SafeProxyFactory } from "safe-contracts/proxies/SafeProxyFactory.sol";
     7  import { ModuleManager } from "safe-contracts/base/ModuleManager.sol";
     8  import { OwnerManager } from "safe-contracts/base/OwnerManager.sol";
     9  import { Enum } from "safe-contracts/common/Enum.sol";
    10  import "test/safe-tools/SafeTestTools.sol";
    11  
    12  import { LivenessModule } from "src/Safe/LivenessModule.sol";
    13  import { LivenessGuard } from "src/Safe/LivenessGuard.sol";
    14  
    15  contract LivenessModule_TestInit is Test, SafeTestTools {
    16      using SafeTestLib for SafeInstance;
    17  
    18      event SignersRecorded(bytes32 indexed txHash, address[] signers);
    19  
    20      uint256 initTime = 10;
    21      uint256 livenessInterval = 30 days;
    22      uint256 minOwners = 6;
    23      LivenessModule livenessModule;
    24      LivenessGuard livenessGuard;
    25      SafeInstance safeInstance;
    26      address fallbackOwner;
    27  
    28      /// @dev Removes an owner from the safe
    29      function _removeAnOwner(address _ownerToRemove, address[] memory _owners) internal {
    30          address[] memory prevOwners = new address[](1);
    31          address[] memory ownersToRemove = new address[](1);
    32          ownersToRemove[0] = _ownerToRemove;
    33          prevOwners[0] = SafeTestLib.getPrevOwnerFromList(_ownerToRemove, _owners);
    34  
    35          livenessModule.removeOwners(prevOwners, ownersToRemove);
    36      }
    37  
    38      /// @dev Set the current time to after the liveness interval
    39      function _warpPastLivenessInterval() internal {
    40          vm.warp(initTime + livenessInterval + 1);
    41      }
    42  
    43      /// @dev Sets up the test environment
    44      function setUp() public virtual {
    45          // Set the block timestamp to the initTime, so that signatures recorded in the first block
    46          // are non-zero.
    47          vm.warp(initTime);
    48  
    49          // Create a Safe with 10 owners
    50          (, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("moduleTest", 10);
    51          safeInstance = _setupSafe(keys, 8);
    52  
    53          livenessGuard = new LivenessGuard(safeInstance.safe);
    54          fallbackOwner = makeAddr("fallbackOwner");
    55          livenessModule = new LivenessModule({
    56              _safe: safeInstance.safe,
    57              _livenessGuard: livenessGuard,
    58              _livenessInterval: livenessInterval,
    59              _minOwners: minOwners,
    60              _fallbackOwner: fallbackOwner
    61          });
    62          safeInstance.setGuard(address(livenessGuard));
    63          safeInstance.enableModule(address(livenessModule));
    64      }
    65  }
    66  
    67  contract LivenessModule_Constructor_TestFail is LivenessModule_TestInit {
    68      /// @dev Tests that the constructor fails if the minOwners is greater than the number of owners
    69      function test_constructor_minOwnersGreaterThanOwners_reverts() external {
    70          vm.expectRevert("LivenessModule: minOwners must be less than the number of owners");
    71          new LivenessModule({
    72              _safe: safeInstance.safe,
    73              _livenessGuard: livenessGuard,
    74              _livenessInterval: livenessInterval,
    75              _minOwners: 11,
    76              _fallbackOwner: address(0)
    77          });
    78      }
    79  
    80      /// @dev Tests that the constructor fails if the minOwners is greater than the number of owners
    81      function test_constructor_wrongThreshold_reverts() external {
    82          uint256 wrongThreshold = livenessModule.get75PercentThreshold(safeInstance.owners.length) - 1;
    83          vm.mockCall(
    84              address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ()), abi.encode(wrongThreshold)
    85          );
    86          vm.expectRevert("LivenessModule: Safe must have a threshold of at least 75% of the number of owners");
    87          new LivenessModule({
    88              _safe: safeInstance.safe,
    89              _livenessGuard: livenessGuard,
    90              _livenessInterval: livenessInterval,
    91              _minOwners: minOwners,
    92              _fallbackOwner: address(0)
    93          });
    94      }
    95  }
    96  
    97  contract LivenessModule_Getters_Test is LivenessModule_TestInit {
    98      /// @dev Tests if the getters work correctly
    99      function test_getters_works() external {
   100          assertEq(address(livenessModule.safe()), address(safeInstance.safe));
   101          assertEq(address(livenessModule.livenessGuard()), address(livenessGuard));
   102          assertEq(livenessModule.livenessInterval(), 30 days);
   103          assertEq(livenessModule.minOwners(), 6);
   104          assertEq(livenessModule.fallbackOwner(), fallbackOwner);
   105      }
   106  }
   107  
   108  contract LivenessModule_CanRemove_TestFail is LivenessModule_TestInit {
   109      /// @dev Tests if canRemove work correctly
   110      function test_canRemove_notSafeOwner_reverts() external {
   111          address nonOwner = makeAddr("nonOwner");
   112          vm.expectRevert("LivenessModule: the owner to remove must be an owner of the Safe");
   113          livenessModule.canRemove(nonOwner);
   114      }
   115  }
   116  
   117  contract LivenessModule_CanRemove_Test is LivenessModule_TestInit {
   118      /// @dev Tests if canRemove work correctly
   119      function test_canRemove_works() external {
   120          _warpPastLivenessInterval();
   121          bool canRemove = livenessModule.canRemove(safeInstance.owners[0]);
   122          assertTrue(canRemove);
   123      }
   124  }
   125  
   126  contract LivenessModule_Get75PercentThreshold_Test is LivenessModule_TestInit {
   127      /// @dev check the return values of the get75PercentThreshold function against manually
   128      ///      calculated values.
   129      function test_get75PercentThreshold_Works() external {
   130          assertEq(livenessModule.get75PercentThreshold(20), 15);
   131          assertEq(livenessModule.get75PercentThreshold(19), 15);
   132          assertEq(livenessModule.get75PercentThreshold(18), 14);
   133          assertEq(livenessModule.get75PercentThreshold(17), 13);
   134          assertEq(livenessModule.get75PercentThreshold(16), 12);
   135          assertEq(livenessModule.get75PercentThreshold(15), 12);
   136          assertEq(livenessModule.get75PercentThreshold(14), 11);
   137          assertEq(livenessModule.get75PercentThreshold(13), 10);
   138          assertEq(livenessModule.get75PercentThreshold(12), 9);
   139          assertEq(livenessModule.get75PercentThreshold(11), 9);
   140          assertEq(livenessModule.get75PercentThreshold(10), 8);
   141          assertEq(livenessModule.get75PercentThreshold(9), 7);
   142          assertEq(livenessModule.get75PercentThreshold(8), 6);
   143          assertEq(livenessModule.get75PercentThreshold(7), 6);
   144          assertEq(livenessModule.get75PercentThreshold(6), 5);
   145          assertEq(livenessModule.get75PercentThreshold(5), 4);
   146          assertEq(livenessModule.get75PercentThreshold(4), 3);
   147          assertEq(livenessModule.get75PercentThreshold(3), 3);
   148          assertEq(livenessModule.get75PercentThreshold(2), 2);
   149          assertEq(livenessModule.get75PercentThreshold(1), 1);
   150      }
   151  }
   152  
   153  contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
   154      using SafeTestLib for SafeInstance;
   155  
   156      /// @dev Tests with different length owner arrays
   157      function test_removeOwners_differentArrayLengths_reverts() external {
   158          address[] memory ownersToRemove = new address[](1);
   159          address[] memory prevOwners = new address[](2);
   160          vm.expectRevert("LivenessModule: arrays must be the same length");
   161          livenessModule.removeOwners(prevOwners, ownersToRemove);
   162      }
   163  
   164      /// @dev Test removing an owner which has recently signed a transaction
   165      function test_removeOwners_ownerHasSignedRecently_reverts() external {
   166          /// Will sign a transaction with the first M owners in the owners list
   167          safeInstance.execTransaction({ to: address(1111), value: 0, data: hex"abba" });
   168  
   169          address[] memory owners = safeInstance.safe.getOwners();
   170  
   171          vm.expectRevert("LivenessModule: the owner to remove has signed recently");
   172          _removeAnOwner(safeInstance.owners[0], owners);
   173      }
   174  
   175      /// @dev Test removing an owner which has recently called showLiveness
   176      function test_removeOwners_ownerHasShownLivenessRecently_reverts() external {
   177          /// Will sign a transaction with the first M owners in the owners list
   178          vm.prank(safeInstance.owners[0]);
   179          livenessGuard.showLiveness();
   180          address[] memory owners = safeInstance.safe.getOwners();
   181          vm.expectRevert("LivenessModule: the owner to remove has signed recently");
   182          _removeAnOwner(safeInstance.owners[0], owners);
   183      }
   184  
   185      /// @dev Test removing an owner with an incorrect previous owner
   186      function test_removeOwners_wrongPreviousOwner_reverts() external {
   187          address[] memory prevOwners = new address[](1);
   188          address[] memory ownersToRemove = new address[](1);
   189          ownersToRemove[0] = safeInstance.owners[0];
   190          prevOwners[0] = ownersToRemove[0]; // incorrect.
   191  
   192          _warpPastLivenessInterval();
   193          vm.expectRevert("LivenessModule: failed to remove owner");
   194          livenessModule.removeOwners(prevOwners, ownersToRemove);
   195      }
   196  
   197      /// @dev Tests if removing all owners works correctly
   198      function test_removeOwners_swapToFallbackOwner_reverts() external {
   199          uint256 numOwners = safeInstance.owners.length;
   200  
   201          address[] memory ownersToRemove = new address[](numOwners);
   202          for (uint256 i; i < numOwners; i++) {
   203              ownersToRemove[i] = safeInstance.owners[i];
   204          }
   205          address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   206  
   207          // Incorrectly set the final owner to address(0)
   208          ownersToRemove[ownersToRemove.length - 1] = address(0);
   209  
   210          _warpPastLivenessInterval();
   211          vm.expectRevert("LivenessModule: failed to swap to fallback owner");
   212          livenessModule.removeOwners(prevOwners, ownersToRemove);
   213      }
   214  
   215      /// @dev Tests if remove owners reverts if it removes too many owners without removing all of them
   216      function test_removeOwners_belowMinButNotEmptied_reverts() external {
   217          // Remove all but one owner
   218          uint256 numOwners = safeInstance.owners.length - 2;
   219  
   220          address[] memory ownersToRemove = new address[](numOwners);
   221          for (uint256 i; i < numOwners; i++) {
   222              ownersToRemove[i] = safeInstance.owners[i];
   223          }
   224          address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   225  
   226          _warpPastLivenessInterval();
   227          vm.expectRevert(
   228              "LivenessModule: must remove all owners and transfer to fallback owner if numOwners < minOwners"
   229          );
   230          livenessModule.removeOwners(prevOwners, ownersToRemove);
   231      }
   232  
   233      /// @dev Tests if remove owners reverts if it removes too many owners transferring to the shutDown owner
   234      function test_removeOwners_belowEmptiedButNotShutDown_reverts() external {
   235          // Remove all but one owner
   236          uint256 numOwners = safeInstance.owners.length - 1;
   237  
   238          address[] memory ownersToRemove = new address[](numOwners);
   239          for (uint256 i; i < numOwners; i++) {
   240              ownersToRemove[i] = safeInstance.owners[i];
   241          }
   242          address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   243  
   244          _warpPastLivenessInterval();
   245          vm.expectRevert("LivenessModule: must transfer ownership to fallback owner");
   246          livenessModule.removeOwners(prevOwners, ownersToRemove);
   247      }
   248  
   249      /// @dev Tests if remove owners reverts if the current Safe.guard does note match the expected
   250      ///      livenessGuard address.
   251      function test_removeOwners_guardChanged_reverts() external {
   252          address[] memory ownersToRemove = new address[](1);
   253          ownersToRemove[0] = safeInstance.owners[0];
   254          address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   255  
   256          // Change the guard
   257          livenessGuard = new LivenessGuard(safeInstance.safe);
   258          safeInstance.setGuard(address(livenessGuard));
   259  
   260          _warpPastLivenessInterval();
   261          vm.expectRevert("LivenessModule: guard has been changed");
   262          livenessModule.removeOwners(prevOwners, ownersToRemove);
   263      }
   264  
   265      function test_removeOwners_invalidThreshold_reverts() external {
   266          address[] memory ownersToRemove = new address[](0);
   267          address[] memory prevOwners = new address[](0);
   268          uint256 wrongThreshold = safeInstance.safe.getThreshold() + 1;
   269  
   270          vm.mockCall(
   271              address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ()), abi.encode(wrongThreshold)
   272          );
   273  
   274          _warpPastLivenessInterval();
   275          vm.expectRevert("LivenessModule: Safe must have a threshold of 75% of the number of owners");
   276          livenessModule.removeOwners(prevOwners, ownersToRemove);
   277      }
   278  }
   279  
   280  contract LivenessModule_RemoveOwners_Test is LivenessModule_TestInit {
   281      using SafeTestLib for SafeInstance;
   282  
   283      /// @dev Tests if removing one owner works correctly
   284      function test_removeOwners_oneOwner_succeeds() external {
   285          uint256 ownersBefore = safeInstance.owners.length;
   286          address ownerToRemove = safeInstance.owners[0];
   287  
   288          _warpPastLivenessInterval();
   289          _removeAnOwner(ownerToRemove, safeInstance.owners);
   290  
   291          assertFalse(safeInstance.safe.isOwner(ownerToRemove));
   292          assertEq(safeInstance.safe.getOwners().length, ownersBefore - 1);
   293      }
   294  
   295      /// @dev Tests if removing all owners works correctly
   296      function test_removeOwners_allOwners_succeeds() external {
   297          uint256 numOwners = safeInstance.owners.length;
   298  
   299          address[] memory ownersToRemove = new address[](numOwners);
   300          for (uint256 i; i < numOwners; i++) {
   301              ownersToRemove[i] = safeInstance.owners[i];
   302          }
   303          address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   304  
   305          _warpPastLivenessInterval();
   306          livenessModule.removeOwners(prevOwners, ownersToRemove);
   307          assertEq(safeInstance.safe.getOwners().length, 1);
   308          assertEq(safeInstance.safe.getOwners()[0], fallbackOwner);
   309          assertEq(safeInstance.safe.getThreshold(), 1);
   310      }
   311  }
   312  
   313  /// @dev A copy of LivenessModule.get75PercentThreshold as a free function to use below.
   314  function get75PercentThreshold(uint256 _numOwners) pure returns (uint256 threshold_) {
   315      threshold_ = (_numOwners * 75 + 99) / 100;
   316  }
   317  
   318  contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
   319      using SafeTestLib for SafeInstance;
   320  
   321      /// @dev We put this array in storage so that we can more easily populate it using push in the tests below.
   322      address[] ownersToRemove;
   323  
   324      /// @dev Options for handling the event that the number of owners remaining is less than minOwners
   325      enum ShutDownBehavior {
   326          // Correctly removes the owners and transfers to the shutDown owner
   327          Correct,
   328          // Removes all but one owner, and does not transfer to the shutDown owner
   329          DoesNotTransferToFallbackOwner,
   330          // Leaves more than one owner when below minOwners
   331          DoesNotRemoveAllOwners
   332      }
   333  
   334      /// @dev This contract inherits the storage layout from the LivenessModule_TestInit contract, but we
   335      ///      override the base setUp function, to avoid instantiating an unnecessary Safe and liveness checking system.
   336      function setUp() public override {
   337          vm.warp(initTime);
   338          fallbackOwner = makeAddr("fallbackOwner");
   339      }
   340  
   341      /// @dev Extracts the setup of the test environment into a separate function.
   342      function _prepare(
   343          uint256 _numOwners,
   344          uint256 _minOwners,
   345          uint256 _numLiveOwners
   346      )
   347          internal
   348          returns (uint256 numOwners_, uint256 minOwners_, uint256 numLiveOwners_)
   349      {
   350          // First we modify the test parameters to ensure that they describe a plausible starting point.
   351          //
   352          // _numOwners must be at least 4, so that _minOwners can be set to at least 3 by the following bound() call.
   353          // Limiting the owner set to 20 helps to keep the runtime of the test reasonable.
   354          numOwners_ = bound(_numOwners, 4, 20);
   355          // _minOwners must be at least 3, otherwise we don't have any range below _minOwners in which to test all of the
   356          // ShutDownBehavior options.
   357          minOwners_ = bound(_minOwners, 3, numOwners_ - 1);
   358  
   359          // Ensure that _numLiveOwners is less than _numOwners so that we can remove at least one owner.
   360          numLiveOwners_ = bound(_numLiveOwners, 0, numOwners_ - 1);
   361  
   362          // The above bounds are a bit tricky, so we assert that the resulting parameters enable us to test all possible
   363          // success and revert cases in the removeOwners function.
   364          // This is also necessary to avoid underflows or out of bounds accesses in the test.
   365          assertTrue(
   366              numOwners_ > minOwners_ // We need to be able to remove at least one owner
   367                  && numOwners_ >= numLiveOwners_ // We can have more live owners than there are owners
   368                  && minOwners_ >= 3 // Allows us to test all of the ShutDownBehavior options when removing an owner
   369          );
   370  
   371          // Create a Safe with _numOwners owners
   372          (, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("rmOwnersTest", numOwners_);
   373          uint256 threshold = get75PercentThreshold(numOwners_);
   374          safeInstance = _setupSafe(keys, threshold);
   375          livenessGuard = new LivenessGuard(safeInstance.safe);
   376          livenessModule = new LivenessModule({
   377              _safe: safeInstance.safe,
   378              _livenessGuard: livenessGuard,
   379              _livenessInterval: livenessInterval,
   380              _minOwners: minOwners_,
   381              _fallbackOwner: fallbackOwner
   382          });
   383          safeInstance.setGuard(address(livenessGuard));
   384          safeInstance.enableModule(address(livenessModule));
   385  
   386          // Warp ahead so that all owners non-live
   387          _warpPastLivenessInterval();
   388      }
   389  
   390      /// @dev Tests if removing owners works correctly for various safe configurations and numbeers of live owners
   391      function testFuzz_removeOwners(
   392          uint256 _numOwners,
   393          uint256 _minOwners,
   394          uint256 _numLiveOwners,
   395          uint256 _shutDownBehavior,
   396          uint256 _numOwnersToRemoveinShutDown
   397      )
   398          external
   399      {
   400          // Prepare the test env and test params
   401          (uint256 numOwners, uint256 minOwners, uint256 numLiveOwners) = _prepare(_numOwners, _minOwners, _numLiveOwners);
   402  
   403          // Create an array of live owners, and call showLiveness for each of them
   404          address[] memory liveOwners = new address[](numLiveOwners);
   405          for (uint256 i; i < numLiveOwners; i++) {
   406              liveOwners[i] = safeInstance.owners[i];
   407              vm.prank(safeInstance.owners[i]);
   408              livenessGuard.showLiveness();
   409          }
   410  
   411          address[] memory nonLiveOwners = new address[](numOwners - numLiveOwners);
   412          for (uint256 i; i < numOwners - numLiveOwners; i++) {
   413              nonLiveOwners[i] = safeInstance.owners[i + numLiveOwners];
   414          }
   415  
   416          address[] memory prevOwners;
   417          if (numLiveOwners >= minOwners) {
   418              // The safe will remain above the minimum number of owners, so we can remove only those owners which are not
   419              // live.
   420              prevOwners = safeInstance.getPrevOwners(nonLiveOwners);
   421              livenessModule.removeOwners(prevOwners, nonLiveOwners);
   422  
   423              // Validate the resulting state of the Safe
   424              assertEq(safeInstance.safe.getOwners().length, numLiveOwners);
   425              assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numLiveOwners));
   426              for (uint256 i; i < numLiveOwners; i++) {
   427                  assertTrue(safeInstance.safe.isOwner(liveOwners[i]));
   428              }
   429              for (uint256 i; i < nonLiveOwners.length; i++) {
   430                  assertFalse(safeInstance.safe.isOwner(nonLiveOwners[i]));
   431              }
   432          } else {
   433              // The number of non-live owners will push the safe below the minimum number of owners.
   434              // We need to test all of the possible ShutDownBehavior options, so we'll create a ShutDownBehavior enum
   435              // from the _shutDownBehavior input.
   436              ShutDownBehavior shutDownBehavior =
   437                  ShutDownBehavior(bound(_shutDownBehavior, 0, uint256(type(ShutDownBehavior).max)));
   438              // The safe is below the minimum number of owners.
   439              // The ShutDownBehavior enum determines how we handle this case.
   440              if (shutDownBehavior == ShutDownBehavior.Correct) {
   441                  // We remove all owners, and transfer ownership to the shutDown owner.
   442                  // but we need to do remove the non-live owners first, so we reverse the owners array, since
   443                  // the first owners in the array were the ones to call showLiveness.
   444                  for (uint256 i; i < numOwners; i++) {
   445                      ownersToRemove.push(safeInstance.owners[numOwners - i - 1]);
   446                  }
   447                  prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   448                  livenessModule.removeOwners(prevOwners, ownersToRemove);
   449  
   450                  // Validate the resulting state of the Safe
   451                  assertEq(safeInstance.safe.getOwners().length, 1);
   452                  assertEq(safeInstance.safe.getOwners()[0], fallbackOwner);
   453                  assertEq(safeInstance.safe.getThreshold(), 1);
   454              } else {
   455                  // For both of the incorrect behaviors, we need to calculate the number of owners to remove to
   456                  // trigger that behavior. We initialize that value here then set it in the if statements below.
   457                  uint256 numOwnersToRemoveinShutDown;
   458                  if (shutDownBehavior == ShutDownBehavior.DoesNotRemoveAllOwners) {
   459                      // In the DoesNotRemoveAllOwners case, we should have more than 1 of the pre-existing owners
   460                      // remaining
   461                      numOwnersToRemoveinShutDown =
   462                          bound(_numOwnersToRemoveinShutDown, numOwners - minOwners + 1, numOwners - 2);
   463                      for (uint256 i; i < numOwnersToRemoveinShutDown; i++) {
   464                          // Add non-live owners to remove first
   465                          if (i < nonLiveOwners.length) {
   466                              ownersToRemove.push(nonLiveOwners[i]);
   467                          } else {
   468                              // Then add live owners to remove
   469                              ownersToRemove.push(liveOwners[i - nonLiveOwners.length]);
   470                          }
   471                      }
   472                      prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   473                      vm.expectRevert(
   474                          "LivenessModule: must remove all owners and transfer to fallback owner if numOwners < minOwners"
   475                      );
   476                      livenessModule.removeOwners(prevOwners, ownersToRemove);
   477                  } else if (shutDownBehavior == ShutDownBehavior.DoesNotTransferToFallbackOwner) {
   478                      // In the DoesNotRemoveAllOwners case, we should have exactly 1 pre-existing owners remaining
   479                      numOwnersToRemoveinShutDown = numOwners - 1;
   480                      for (uint256 i; i < numOwnersToRemoveinShutDown; i++) {
   481                          // Add non-live owners to remove first
   482                          if (i < nonLiveOwners.length) {
   483                              ownersToRemove.push(nonLiveOwners[i]);
   484                          } else {
   485                              // Then add live owners to remove
   486                              ownersToRemove.push(liveOwners[i - nonLiveOwners.length]);
   487                          }
   488                      }
   489                      prevOwners = safeInstance.getPrevOwners(ownersToRemove);
   490                      vm.expectRevert("LivenessModule: must transfer ownership to fallback owner");
   491                      livenessModule.removeOwners(prevOwners, ownersToRemove);
   492                  }
   493                  // For both of the incorrect behaviors, verify no change to the Safe state
   494                  assertEq(safeInstance.safe.getOwners().length, numOwners);
   495                  assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numOwners));
   496                  for (uint256 i; i < numOwners; i++) {
   497                      assertTrue(safeInstance.safe.isOwner(safeInstance.owners[i]));
   498                  }
   499              }
   500          }
   501      }
   502  }