github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/Safe/LivenessModule.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 import { Safe, OwnerManager } from "safe-contracts/Safe.sol"; 5 import { Enum } from "safe-contracts/common/Enum.sol"; 6 import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; 7 import { LivenessGuard } from "src/Safe/LivenessGuard.sol"; 8 import { ISemver } from "src/universal/ISemver.sol"; 9 10 /// @title LivenessModule 11 /// @notice This module is intended to be used in conjunction with the LivenessGuard. In the event 12 /// that an owner of the safe is not recorded by the guard during the liveness interval, 13 /// the owner will be considered inactive and will be removed from the list of owners. 14 /// If the number of owners falls below the minimum number of owners, the ownership of the 15 /// safe will be transferred to the fallback owner. 16 contract LivenessModule is ISemver { 17 /// @notice The Safe contract instance 18 Safe internal immutable SAFE; 19 20 /// @notice The LivenessGuard contract instance 21 /// This can be updated by replacing with a new module and switching out the guard. 22 LivenessGuard internal immutable LIVENESS_GUARD; 23 24 /// @notice The interval, in seconds, during which an owner must have demonstrated liveness 25 /// This can be updated by replacing with a new module. 26 uint256 internal immutable LIVENESS_INTERVAL; 27 28 /// @notice The minimum number of owners before ownership of the safe is transferred to the fallback owner. 29 /// This can be updated by replacing with a new module. 30 uint256 internal immutable MIN_OWNERS; 31 32 /// @notice The fallback owner of the Safe 33 /// This can be updated by replacing with a new module. 34 address internal immutable FALLBACK_OWNER; 35 36 /// @notice The storage slot used in the safe to store the guard address 37 /// keccak256("guard_manager.guard.address") 38 uint256 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; 39 40 /// @notice Semantic version. 41 /// @custom:semver 1.0.0 42 string public constant version = "1.0.0"; 43 44 // Constructor to initialize the Safe and baseModule instances 45 constructor( 46 Safe _safe, 47 LivenessGuard _livenessGuard, 48 uint256 _livenessInterval, 49 uint256 _minOwners, 50 address _fallbackOwner 51 ) { 52 SAFE = _safe; 53 LIVENESS_GUARD = _livenessGuard; 54 LIVENESS_INTERVAL = _livenessInterval; 55 FALLBACK_OWNER = _fallbackOwner; 56 MIN_OWNERS = _minOwners; 57 address[] memory owners = _safe.getOwners(); 58 require(_minOwners <= owners.length, "LivenessModule: minOwners must be less than the number of owners"); 59 require( 60 _safe.getThreshold() >= get75PercentThreshold(owners.length), 61 "LivenessModule: Safe must have a threshold of at least 75% of the number of owners" 62 ); 63 } 64 65 /// @notice For a given number of owners, return the lowest threshold which is greater than 75. 66 /// Note: this function returns 1 for numOwners == 1. 67 function get75PercentThreshold(uint256 _numOwners) public pure returns (uint256 threshold_) { 68 threshold_ = (_numOwners * 75 + 99) / 100; 69 } 70 71 /// @notice Getter function for the Safe contract instance 72 /// @return safe_ The Safe contract instance 73 function safe() public view returns (Safe safe_) { 74 safe_ = SAFE; 75 } 76 77 /// @notice Getter function for the LivenessGuard contract instance 78 /// @return livenessGuard_ The LivenessGuard contract instance 79 function livenessGuard() public view returns (LivenessGuard livenessGuard_) { 80 livenessGuard_ = LIVENESS_GUARD; 81 } 82 83 /// @notice Getter function for the liveness interval 84 /// @return livenessInterval_ The liveness interval, in seconds 85 function livenessInterval() public view returns (uint256 livenessInterval_) { 86 livenessInterval_ = LIVENESS_INTERVAL; 87 } 88 89 /// @notice Getter function for the minimum number of owners 90 /// @return minOwners_ The minimum number of owners 91 function minOwners() public view returns (uint256 minOwners_) { 92 minOwners_ = MIN_OWNERS; 93 } 94 95 /// @notice Getter function for the fallback owner 96 /// @return fallbackOwner_ The fallback owner of the Safe 97 function fallbackOwner() public view returns (address fallbackOwner_) { 98 fallbackOwner_ = FALLBACK_OWNER; 99 } 100 101 /// @notice Checks if the owner can be removed 102 /// @param _owner The owner to be removed 103 /// @return canRemove_ bool indicating if the owner can be removed 104 function canRemove(address _owner) public view returns (bool canRemove_) { 105 require(SAFE.isOwner(_owner), "LivenessModule: the owner to remove must be an owner of the Safe"); 106 canRemove_ = LIVENESS_GUARD.lastLive(_owner) + LIVENESS_INTERVAL < block.timestamp; 107 } 108 109 /// @notice This function can be called by anyone to remove a set of owners that have not signed a transaction 110 /// during the liveness interval. If the number of owners drops below the minimum, then all owners 111 /// must be removed. 112 /// @param _previousOwners The previous owners in the linked list of owners 113 /// @param _ownersToRemove The owners to remove 114 function removeOwners(address[] memory _previousOwners, address[] memory _ownersToRemove) external { 115 require(_previousOwners.length == _ownersToRemove.length, "LivenessModule: arrays must be the same length"); 116 117 // Initialize the ownersCount count to the current number of owners, so that we can track the number of 118 // owners in the Safe after each removal. The Safe will revert if an owner cannot be removed, so it is safe 119 // keep a local count of the number of owners this way. 120 uint256 ownersCount = SAFE.getOwners().length; 121 for (uint256 i = 0; i < _previousOwners.length; i++) { 122 // Validate that the owner can be removed, which means that either: 123 // 1. the ownersCount is now less than MIN_OWNERS, in which case all owners should be removed regardless 124 // of liveness, 125 // 2. the owner has not signed a transaction during the liveness interval. 126 if (ownersCount >= MIN_OWNERS) { 127 require(canRemove(_ownersToRemove[i]), "LivenessModule: the owner to remove has signed recently"); 128 } 129 130 // Pre-emptively update our local count of the number of owners. 131 // This is safe because _removeOwner will bubble up any revert from the Safe if the owner cannot be removed. 132 ownersCount--; 133 134 // We now attempt remove the owner from the safe. 135 _removeOwner({ 136 _prevOwner: _previousOwners[i], 137 _ownerToRemove: _ownersToRemove[i], 138 _newOwnersCount: ownersCount 139 }); 140 141 // when all owners are removed and the sole owner is the fallback owner, the 142 // ownersCount variable will be incorrectly set to zero. 143 // This reflects the fact that all prior owners have been removed. The loop should naturally exit at this 144 // point, but for safety we detect this condition and force the loop to terminate. 145 if (ownersCount == 0) { 146 break; 147 } 148 } 149 _verifyFinalState(); 150 } 151 152 /// @notice Removes an owner from the Safe and updates the threshold. 153 /// @param _prevOwner Owner that pointed to the owner to be removed in the linked list 154 /// @param _ownerToRemove Owner address to be removed. 155 /// @param _newOwnersCount New number of owners after removal. 156 function _removeOwner(address _prevOwner, address _ownerToRemove, uint256 _newOwnersCount) internal { 157 if (_newOwnersCount > 0) { 158 uint256 newThreshold = get75PercentThreshold(_newOwnersCount); 159 // Remove the owner and update the threshold 160 _removeOwnerSafeCall({ _prevOwner: _prevOwner, _owner: _ownerToRemove, _threshold: newThreshold }); 161 } else { 162 // There is only one owner left. The Safe will not allow a safe with no owners, so we will 163 // need to swap owners instead. 164 _swapToFallbackOwnerSafeCall({ _prevOwner: _prevOwner, _oldOwner: _ownerToRemove }); 165 } 166 } 167 168 /// @notice Sets the fallback owner as the sole owner of the Safe with a threshold of 1 169 /// @param _prevOwner Owner that pointed to the owner to be replaced in the linked list 170 /// @param _oldOwner Owner address to be replaced. 171 function _swapToFallbackOwnerSafeCall(address _prevOwner, address _oldOwner) internal { 172 require( 173 SAFE.execTransactionFromModule({ 174 to: address(SAFE), 175 value: 0, 176 operation: Enum.Operation.Call, 177 data: abi.encodeCall(OwnerManager.swapOwner, (_prevOwner, _oldOwner, FALLBACK_OWNER)) 178 }), 179 "LivenessModule: failed to swap to fallback owner" 180 ); 181 } 182 183 /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`. 184 /// @param _prevOwner Owner that pointed to the owner to be removed in the linked list 185 /// @param _owner Owner address to be removed. 186 /// @param _threshold New threshold. 187 function _removeOwnerSafeCall(address _prevOwner, address _owner, uint256 _threshold) internal { 188 require( 189 SAFE.execTransactionFromModule({ 190 to: address(SAFE), 191 value: 0, 192 operation: Enum.Operation.Call, 193 data: abi.encodeCall(OwnerManager.removeOwner, (_prevOwner, _owner, _threshold)) 194 }), 195 "LivenessModule: failed to remove owner" 196 ); 197 } 198 199 /// @notice A FREI-PI invariant check enforcing requirements on number of owners and threshold. 200 function _verifyFinalState() internal view { 201 address[] memory owners = SAFE.getOwners(); 202 uint256 numOwners = owners.length; 203 204 // Ensure that the safe is not being left in a safe state such that either: 205 // 1. there are at least the minimum number of owners, or 206 // 2. there is a single owner and that owner is the fallback owner. 207 if (numOwners == 1) { 208 require(owners[0] == FALLBACK_OWNER, "LivenessModule: must transfer ownership to fallback owner"); 209 } else { 210 require( 211 numOwners >= MIN_OWNERS, 212 "LivenessModule: must remove all owners and transfer to fallback owner if numOwners < minOwners" 213 ); 214 } 215 216 // Check that"LivenessModule: must remove all owners and transfer to fallback owner if numOwners < minOwners" 217 // the threshold is correct. This check is also correct when there is a single 218 // owner, because get75PercentThreshold(1) returns 1. 219 uint256 threshold = SAFE.getThreshold(); 220 require( 221 threshold == get75PercentThreshold(numOwners), 222 "LivenessModule: Safe must have a threshold of 75% of the number of owners" 223 ); 224 225 // Check that the guard has not been changed 226 require( 227 address(LIVENESS_GUARD) == address(uint160(uint256(bytes32(SAFE.getStorageAt(GUARD_STORAGE_SLOT, 1))))), 228 "LivenessModule: guard has been changed" 229 ); 230 } 231 }