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 }