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 }