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  }