github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/safe-tools/SafeTestTools.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity >=0.7.0 <0.9.0; 3 4 import "forge-std/Test.sol"; 5 import { LibSort } from "solady/utils/LibSort.sol"; 6 import { Safe as GnosisSafe, OwnerManager, ModuleManager, GuardManager } from "safe-contracts/Safe.sol"; 7 import { SafeProxyFactory as GnosisSafeProxyFactory } from "safe-contracts/proxies/SafeProxyFactory.sol"; 8 import { Enum } from "safe-contracts/common/Enum.sol"; 9 import { SignMessageLib } from "safe-contracts/libraries/SignMessageLib.sol"; 10 import "./CompatibilityFallbackHandler_1_3_0.sol"; 11 12 // Tools to simplify testing Safe contracts 13 // Author: Colin Nielsen (https://github.com/colinnielsen/safe-tools) 14 // With expanded and improved functionality by OP Labs 15 16 /// @dev A minimal wrapper around the OwnerManager contract. This contract is meant to be initialized with 17 /// the same owners as a Safe instance, and then used to simulate the resulting owners list 18 /// after an owner is removed. 19 contract OwnerSimulator is OwnerManager { 20 constructor(address[] memory _owners, uint256 _threshold) { 21 setupOwners(_owners, _threshold); 22 } 23 24 /// @dev Exposes the OwnerManager's removeOwner function so that anyone may call without needing auth 25 function removeOwnerWrapped(address prevOwner, address owner, uint256 _threshold) public { 26 OwnerManager(address(this)).removeOwner(prevOwner, owner, _threshold); 27 } 28 } 29 30 /// @dev collapsed interface that includes comapatibilityfallback handler calls 31 abstract contract DeployedSafe is GnosisSafe, CompatibilityFallbackHandler { } 32 33 struct AdvancedSafeInitParams { 34 bool includeFallbackHandler; 35 uint256 saltNonce; 36 address setupModulesCall_to; 37 bytes setupModulesCall_data; 38 uint256 refundAmount; 39 address refundToken; 40 address payable refundReceiver; 41 bytes initData; 42 } 43 44 struct SafeInstance { 45 uint256 instanceId; 46 uint256[] ownerPKs; 47 address[] owners; 48 uint256 threshold; 49 DeployedSafe safe; 50 } 51 52 library Sort { 53 /// @dev Sorts an array of addresses in place 54 function sort(address[] memory arr) public pure returns (address[] memory) { 55 LibSort.sort(arr); 56 return arr; 57 } 58 } 59 60 library SafeTestLib { 61 /// @dev The address of foundry's VM contract 62 address constant VM_ADDR = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; 63 /// @dev The address of the first owner in the linked list of owners 64 address constant SENTINEL_OWNERS = address(0x1); 65 66 /// @dev Get the address from a private key 67 function getAddr(uint256 pk) internal pure returns (address) { 68 return Vm(VM_ADDR).addr(pk); 69 } 70 71 /// @dev Get arrays of addresses and private keys. The arrays are sorted by address, and the addresses are labelled 72 function makeAddrsAndKeys( 73 string memory prefix, 74 uint256 num 75 ) 76 internal 77 returns (address[] memory addrs, uint256[] memory keys) 78 { 79 keys = new uint256[](num); 80 addrs = new address[](num); 81 for (uint256 i; i < num; i++) { 82 uint256 key = uint256(keccak256(abi.encodePacked(i))); 83 keys[i] = key; 84 } 85 86 for (uint256 i; i < num; i++) { 87 addrs[i] = Vm(VM_ADDR).addr(keys[i]); 88 Vm(VM_ADDR).label(getAddr(keys[i]), string.concat(prefix, Vm(VM_ADDR).toString(i))); 89 } 90 } 91 92 bytes12 constant ADDR_MASK = 0xffffffffffffffffffffffff; 93 94 /// @dev Encode a smart contract wallet as a private key 95 function encodeSmartContractWalletAsPK(address addr) internal pure returns (uint256 encodedPK) { 96 assembly { 97 let addr_b32 := addr 98 encodedPK := or(addr, ADDR_MASK) 99 } 100 } 101 102 /// @dev Decode a smart contract wallet as an address from a private key 103 function decodeSmartContractWalletAsAddress(uint256 pk) internal pure returns (address decodedAddr) { 104 assembly { 105 let addr := shl(96, pk) 106 decodedAddr := shr(96, addr) 107 } 108 } 109 110 /// @dev Checks if a private key is an encoded smart contract address 111 function isSmartContractPK(uint256 pk) internal pure returns (bool isEncoded) { 112 assembly { 113 isEncoded := eq(shr(160, pk), shr(160, ADDR_MASK)) 114 } 115 } 116 117 /// @dev Sorts an array of private keys by the computed address 118 /// If the private key is a smart contract wallet, it will be decoded and sorted by the address 119 function sortPKsByComputedAddress(uint256[] memory _pks) internal pure returns (uint256[] memory) { 120 uint256[] memory sortedPKs = new uint256[](_pks.length); 121 122 address[] memory addresses = new address[](_pks.length); 123 bytes32[2][] memory accounts = new bytes32[2][](_pks.length); 124 125 for (uint256 i; i < _pks.length; i++) { 126 uint256 pk = _pks[i]; 127 address signer = SafeTestLib.getAddr(pk); 128 if (isSmartContractPK(pk)) { 129 signer = decodeSmartContractWalletAsAddress(pk); 130 } 131 addresses[i] = signer; 132 accounts[i][0] = bytes32(abi.encode(signer)); 133 accounts[i][1] = bytes32(pk); 134 } 135 136 addresses = Sort.sort(addresses); 137 138 uint256 found; 139 for (uint256 j; j < addresses.length; j++) { 140 address signer = addresses[j]; 141 uint256 pk; 142 for (uint256 k; k < accounts.length; k++) { 143 if (address(uint160(uint256(accounts[k][0]))) == signer) { 144 pk = uint256(accounts[k][1]); 145 found++; 146 } 147 } 148 149 sortedPKs[j] = pk; 150 } 151 152 if (found < _pks.length) { 153 revert("SAFETESTTOOLS: issue with private key sorting, please open a ticket on github"); 154 } 155 return sortedPKs; 156 } 157 158 /// @dev Sign a transaction as a safe owner with a private key. 159 function signTransaction( 160 SafeInstance memory instance, 161 uint256 pk, 162 address to, 163 uint256 value, 164 bytes memory data, 165 Enum.Operation operation, 166 uint256 safeTxGas, 167 uint256 baseGas, 168 uint256 gasPrice, 169 address gasToken, 170 address refundReceiver 171 ) 172 internal 173 view 174 returns (uint8 v, bytes32 r, bytes32 s) 175 { 176 bytes32 txDataHash; 177 { 178 uint256 _nonce = instance.safe.nonce(); 179 txDataHash = instance.safe.getTransactionHash({ 180 to: to, 181 value: value, 182 data: data, 183 operation: operation, 184 safeTxGas: safeTxGas, 185 baseGas: baseGas, 186 gasPrice: gasPrice, 187 gasToken: gasToken, 188 refundReceiver: refundReceiver, 189 _nonce: _nonce 190 }); 191 } 192 193 (v, r, s) = Vm(VM_ADDR).sign(pk, txDataHash); 194 } 195 196 /// @dev Get the previous owner in the linked list of owners. 197 /// This version of getPrevOwner will call to the Safe contract to get the current list of owners. 198 /// Note that this will break vm.expectRevert() tests by making a call which does not revert.. 199 /// @param _owner The owner whose previous owner we want to find 200 function getPrevOwner(SafeInstance memory instance, address _owner) internal view returns (address prevOwner_) { 201 address[] memory owners = instance.safe.getOwners(); 202 prevOwner_ = getPrevOwnerFromList(_owner, owners); 203 } 204 205 /// @dev Get the previous owner in the provided list of owners. 206 /// This version of getPrevOwner accepts a list of owners, and will return the previous owner. 207 /// It is useful when testing for a revert, as it avoids the need to call to the Safe contract. 208 /// @param _owner The owner whose previous owner we want to find 209 /// @param _ownersList The list of owners to search in 210 function getPrevOwnerFromList( 211 address _owner, 212 address[] memory _ownersList 213 ) 214 internal 215 pure 216 returns (address prevOwner_) 217 { 218 for (uint256 i; i < _ownersList.length; i++) { 219 if (_ownersList[i] != _owner) continue; 220 if (i == 0) { 221 prevOwner_ = SENTINEL_OWNERS; 222 break; 223 } 224 prevOwner_ = _ownersList[i - 1]; 225 } 226 } 227 228 /// @dev Given an array of owners to remove, this function will return an array of the previous owners 229 /// in the order that they must be provided to the LivenessMoules's removeOwners() function. 230 /// Because owners are removed one at a time, and not necessarily in order, we need to simulate 231 /// the owners list after each removal, in order to identify the correct previous owner. 232 /// @param _ownersToRemove The owners to remove 233 /// @return prevOwners_ The previous owners in the linked list 234 function getPrevOwners( 235 SafeInstance memory instance, 236 address[] memory _ownersToRemove 237 ) 238 internal 239 returns (address[] memory prevOwners_) 240 { 241 OwnerSimulator ownerSimulator = new OwnerSimulator(instance.owners, 1); 242 prevOwners_ = new address[](_ownersToRemove.length); 243 address[] memory currentOwners; 244 for (uint256 i; i < _ownersToRemove.length; i++) { 245 currentOwners = ownerSimulator.getOwners(); 246 prevOwners_[i] = SafeTestLib.getPrevOwnerFromList(_ownersToRemove[i], currentOwners); 247 248 // Don't try to remove the last owner 249 if (currentOwners.length == 1) break; 250 ownerSimulator.removeOwnerWrapped(prevOwners_[i], _ownersToRemove[i], 1); 251 } 252 } 253 254 /// @dev Enables a module on the Safe. 255 function enableModule(SafeInstance memory instance, address module) internal { 256 execTransaction( 257 instance, 258 address(instance.safe), 259 0, 260 abi.encodeWithSelector(ModuleManager.enableModule.selector, module), 261 Enum.Operation.Call, 262 0, 263 0, 264 0, 265 address(0), 266 address(0), 267 "" 268 ); 269 } 270 271 /// @dev Disables a module on the Safe. 272 function disableModule(SafeInstance memory instance, address module) internal { 273 (address[] memory modules,) = instance.safe.getModulesPaginated(SENTINEL_MODULES, 1000); 274 address prevModule = SENTINEL_MODULES; 275 bool moduleFound; 276 for (uint256 i; i < modules.length; i++) { 277 if (modules[i] == module) { 278 moduleFound = true; 279 break; 280 } 281 prevModule = modules[i]; 282 } 283 if (!moduleFound) revert("SAFETESTTOOLS: cannot disable module that is not enabled"); 284 285 execTransaction( 286 instance, 287 address(instance.safe), 288 0, 289 abi.encodeWithSelector(ModuleManager.disableModule.selector, prevModule, module), 290 Enum.Operation.Call, 291 0, 292 0, 293 0, 294 address(0), 295 address(0), 296 "" 297 ); 298 } 299 300 /// @dev Sets the guard address on the Safe. Unlike modules there can only be one guard, so 301 /// this method will remove the previous guard. If the guard is set to the 0 address, the 302 /// guard will be disabled. 303 function setGuard(SafeInstance memory instance, address guard) internal { 304 execTransaction( 305 instance, 306 address(instance.safe), 307 0, 308 abi.encodeWithSelector(GuardManager.setGuard.selector, guard), 309 Enum.Operation.Call, 310 0, 311 0, 312 0, 313 address(0), 314 address(0), 315 "" 316 ); 317 } 318 319 /// @dev Signs message data using EIP1271: Standard Signature Validation Method for Contracts 320 function EIP1271Sign(SafeInstance memory instance, bytes memory data) internal { 321 address signMessageLib = address(new SignMessageLib()); 322 execTransaction({ 323 instance: instance, 324 to: signMessageLib, 325 value: 0, 326 data: abi.encodeWithSelector(SignMessageLib.signMessage.selector, data), 327 operation: Enum.Operation.DelegateCall, 328 safeTxGas: 0, 329 baseGas: 0, 330 gasPrice: 0, 331 gasToken: address(0), 332 refundReceiver: payable(address(0)), 333 signatures: "" 334 }); 335 } 336 337 /// @dev Signs a data hash using EIP1271: Standard Signature Validation Method for Contracts 338 function EIP1271Sign(SafeInstance memory instance, bytes32 digest) internal { 339 EIP1271Sign(instance, abi.encodePacked(digest)); 340 } 341 342 /// @dev Increments the nonce of the Safe by sending an empty transaction. 343 function incrementNonce(SafeInstance memory instance) internal returns (uint256 newNonce) { 344 execTransaction(instance, address(0), 0, "", Enum.Operation.Call, 0, 0, 0, address(0), address(0), ""); 345 return instance.safe.nonce(); 346 } 347 348 /// @dev Adds a new owner to the safe 349 function changeThreshold(SafeInstance memory instance, uint256 threshold) internal { 350 execTransaction( 351 instance, 352 address(instance.safe), 353 0, 354 abi.encodeWithSelector(OwnerManager.changeThreshold.selector, threshold) 355 ); 356 } 357 358 /// @dev Adds a new owner to the safe 359 function addOwnerWithThreshold(SafeInstance memory instance, address owner, uint256 threshold) internal { 360 execTransaction( 361 instance, 362 address(instance.safe), 363 0, 364 abi.encodeWithSelector(OwnerManager.addOwnerWithThreshold.selector, owner, threshold) 365 ); 366 } 367 368 /// @dev Removes an owner from the safe. If not provided explictly, the identification of the prevOwner is handled 369 /// automatically. 370 function removeOwner(SafeInstance memory instance, address prevOwner, address owner, uint256 threshold) internal { 371 prevOwner = prevOwner > address(0) ? prevOwner : SafeTestLib.getPrevOwner(instance, owner); 372 execTransaction( 373 instance, 374 address(instance.safe), 375 0, 376 abi.encodeWithSelector(OwnerManager.removeOwner.selector, prevOwner, owner, threshold) 377 ); 378 } 379 380 /// @dev Replaces an old owner with a new owner. If not provided explictly, the identification of the prevOwner is 381 /// handled automatically. 382 function swapOwner(SafeInstance memory instance, address prevOwner, address oldOwner, address newOwner) internal { 383 prevOwner = prevOwner > address(0) ? prevOwner : SafeTestLib.getPrevOwner(instance, oldOwner); 384 execTransaction( 385 instance, 386 address(instance.safe), 387 0, 388 abi.encodeWithSelector(OwnerManager.swapOwner.selector, prevOwner, oldOwner, newOwner) 389 ); 390 } 391 392 /// @dev A wrapper for the full execTransaction method, if no signatures are provided it will 393 /// generate them for all owners. 394 function execTransaction( 395 SafeInstance memory instance, 396 address to, 397 uint256 value, 398 bytes memory data, 399 Enum.Operation operation, 400 uint256 safeTxGas, 401 uint256 baseGas, 402 uint256 gasPrice, 403 address gasToken, 404 address refundReceiver, 405 bytes memory signatures 406 ) 407 internal 408 returns (bool) 409 { 410 if (instance.owners.length == 0) { 411 revert("SAFETEST: Instance not initialized. Call _setupSafe() to initialize a test safe"); 412 } 413 414 bytes32 safeTxHash; 415 { 416 uint256 _nonce = instance.safe.nonce(); 417 safeTxHash = instance.safe.getTransactionHash({ 418 to: to, 419 value: value, 420 data: data, 421 operation: operation, 422 safeTxGas: safeTxGas, 423 baseGas: baseGas, 424 gasPrice: gasPrice, 425 gasToken: gasToken, 426 refundReceiver: refundReceiver, 427 _nonce: _nonce 428 }); 429 } 430 431 if (signatures.length == 0) { 432 for (uint256 i; i < instance.ownerPKs.length; ++i) { 433 uint256 pk = instance.ownerPKs[i]; 434 (uint8 v, bytes32 r, bytes32 s) = Vm(VM_ADDR).sign(pk, safeTxHash); 435 if (isSmartContractPK(pk)) { 436 v = 0; 437 address addr = decodeSmartContractWalletAsAddress(pk); 438 assembly { 439 r := addr 440 } 441 console.logBytes32(r); 442 } 443 signatures = bytes.concat(signatures, abi.encodePacked(r, s, v)); 444 } 445 } 446 447 return instance.safe.execTransaction({ 448 to: to, 449 value: value, 450 data: data, 451 operation: operation, 452 safeTxGas: safeTxGas, 453 baseGas: baseGas, 454 gasPrice: gasPrice, 455 gasToken: gasToken, 456 refundReceiver: payable(refundReceiver), 457 signatures: signatures 458 }); 459 } 460 461 /// @dev Executes either a CALL or DELEGATECALL transaction. 462 function execTransaction( 463 SafeInstance memory instance, 464 address to, 465 uint256 value, 466 bytes memory data, 467 Enum.Operation operation 468 ) 469 internal 470 returns (bool) 471 { 472 return execTransaction(instance, to, value, data, operation, 0, 0, 0, address(0), address(0), ""); 473 } 474 475 /// @dev Executes a CALL transaction. 476 function execTransaction( 477 SafeInstance memory instance, 478 address to, 479 uint256 value, 480 bytes memory data 481 ) 482 internal 483 returns (bool) 484 { 485 return execTransaction(instance, to, value, data, Enum.Operation.Call, 0, 0, 0, address(0), address(0), ""); 486 } 487 } 488 489 /// @dev SafeTestTools implements a set of helper functions for testing Safe contracts. 490 contract SafeTestTools { 491 using SafeTestLib for SafeInstance; 492 493 GnosisSafe internal singleton = new GnosisSafe(); 494 GnosisSafeProxyFactory internal proxyFactory = new GnosisSafeProxyFactory(); 495 CompatibilityFallbackHandler internal handler = new CompatibilityFallbackHandler(); 496 497 SafeInstance[] internal instances; 498 499 /// @dev can be called to reinitialize the singleton, proxyFactory and handler. Useful for forking. 500 function _initializeSafeTools() internal { 501 singleton = new GnosisSafe(); 502 proxyFactory = new GnosisSafeProxyFactory(); 503 handler = new CompatibilityFallbackHandler(); 504 } 505 506 function _setupSafe( 507 uint256[] memory ownerPKs, 508 uint256 threshold, 509 uint256 initialBalance, 510 AdvancedSafeInitParams memory advancedParams 511 ) 512 public 513 returns (SafeInstance memory) 514 { 515 uint256[] memory sortedPKs = SafeTestLib.sortPKsByComputedAddress(ownerPKs); 516 address[] memory owners = new address[](sortedPKs.length); 517 518 for (uint256 i; i < sortedPKs.length; i++) { 519 if (SafeTestLib.isSmartContractPK(sortedPKs[i])) { 520 owners[i] = SafeTestLib.decodeSmartContractWalletAsAddress(sortedPKs[i]); 521 } else { 522 owners[i] = SafeTestLib.getAddr(sortedPKs[i]); 523 } 524 } 525 // store the initialization parameters 526 527 bytes memory initData = advancedParams.initData.length > 0 528 ? advancedParams.initData 529 : abi.encodeWithSelector( 530 GnosisSafe.setup.selector, 531 owners, 532 threshold, 533 advancedParams.setupModulesCall_to, 534 advancedParams.setupModulesCall_data, 535 advancedParams.includeFallbackHandler ? address(handler) : address(0), 536 advancedParams.refundToken, 537 advancedParams.refundAmount, 538 advancedParams.refundReceiver 539 ); 540 541 DeployedSafe safe0 = DeployedSafe( 542 payable(proxyFactory.createProxyWithNonce(address(singleton), initData, advancedParams.saltNonce)) 543 ); 544 545 SafeInstance memory instance0 = SafeInstance({ 546 instanceId: instances.length, 547 ownerPKs: sortedPKs, 548 owners: owners, 549 threshold: threshold, 550 // setup safe ecosystem, singleton, proxy factory, fallback handler, and create a new safe 551 safe: safe0 552 }); 553 instances.push(instance0); 554 555 Vm(SafeTestLib.VM_ADDR).deal(address(safe0), initialBalance); 556 557 return instance0; 558 } 559 560 function _setupSafe( 561 uint256[] memory ownerPKs, 562 uint256 threshold, 563 uint256 initialBalance 564 ) 565 public 566 returns (SafeInstance memory) 567 { 568 return _setupSafe( 569 ownerPKs, 570 threshold, 571 initialBalance, 572 AdvancedSafeInitParams({ 573 includeFallbackHandler: true, 574 initData: "", 575 saltNonce: 0, 576 setupModulesCall_to: address(0), 577 setupModulesCall_data: "", 578 refundAmount: 0, 579 refundToken: address(0), 580 refundReceiver: payable(address(0)) 581 }) 582 ); 583 } 584 585 function _setupSafe(uint256[] memory ownerPKs, uint256 threshold) public returns (SafeInstance memory) { 586 return _setupSafe( 587 ownerPKs, 588 threshold, 589 10000 ether, 590 AdvancedSafeInitParams({ 591 includeFallbackHandler: true, 592 initData: "", 593 saltNonce: 0, 594 setupModulesCall_to: address(0), 595 setupModulesCall_data: "", 596 refundAmount: 0, 597 refundToken: address(0), 598 refundReceiver: payable(address(0)) 599 }) 600 ); 601 } 602 603 function _setupSafe() public returns (SafeInstance memory) { 604 (, uint256[] memory defaultPKs) = SafeTestLib.makeAddrsAndKeys("default", 3); 605 606 return _setupSafe( 607 defaultPKs, 608 2, 609 10000 ether, 610 AdvancedSafeInitParams({ 611 includeFallbackHandler: true, 612 initData: "", 613 saltNonce: uint256(keccak256(bytes("SAFE TEST"))), 614 setupModulesCall_to: address(0), 615 setupModulesCall_data: "", 616 refundAmount: 0, 617 refundToken: address(0), 618 refundReceiver: payable(address(0)) 619 }) 620 ); 621 } 622 623 function getSafe() public view returns (SafeInstance memory) { 624 if (instances.length == 0) { 625 revert("SAFETESTTOOLS: Test Safe has not been deployed, use _setupSafe() calling safe()"); 626 } 627 return instances[0]; 628 } 629 630 function getSafe(address _safe) public view returns (SafeInstance memory) { 631 for (uint256 i; i < instances.length; ++i) { 632 if (address(instances[i].safe) == _safe) return instances[i]; 633 } 634 revert("SAFETESTTOOLS: Safe instance not found"); 635 } 636 }