github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/Safe/LivenessGuard.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Safe } from "safe-contracts/Safe.sol";
     5  import { BaseGuard, GuardManager } from "safe-contracts/base/GuardManager.sol";
     6  import { ModuleManager } from "safe-contracts/base/ModuleManager.sol";
     7  import { SafeSigners } from "src/Safe/SafeSigners.sol";
     8  import { Enum } from "safe-contracts/common/Enum.sol";
     9  import { ISemver } from "src/universal/ISemver.sol";
    10  import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    11  
    12  /// @title LivenessGuard
    13  /// @notice This Guard contract is used to track the liveness of Safe owners.
    14  /// @dev It keeps track of the last time each owner participated in signing a transaction.
    15  ///      If an owner does not participate in a transaction for a certain period of time, they are considered inactive.
    16  ///      This Guard is intended to be used in conjunction with the LivenessModule contract, but does
    17  ///      not depend on it.
    18  ///      Note: Both `checkTransaction` and `checkAfterExecution` are called once each by the Safe contract
    19  ///      before and after the execution of a transaction. It is critical that neither function revert,
    20  ///      otherwise the Safe contract will be unable to execute a transaction.
    21  contract LivenessGuard is ISemver, BaseGuard {
    22      using EnumerableSet for EnumerableSet.AddressSet;
    23  
    24      /// @notice Emitted when an owner is recorded.
    25      /// @param owner The owner's address.
    26      event OwnerRecorded(address owner);
    27  
    28      /// @notice Semantic version.
    29      /// @custom:semver 1.0.0
    30      string public constant version = "1.0.0";
    31  
    32      /// @notice The safe account for which this contract will be the guard.
    33      Safe internal immutable SAFE;
    34  
    35      /// @notice A mapping of the timestamp at which an owner last participated in signing a
    36      ///         an executed transaction, or called showLiveness.
    37      mapping(address => uint256) public lastLive;
    38  
    39      /// @notice An enumerable set of addresses used to store the list of owners before execution,
    40      ///         and then to update the lastLive mapping according to changes in the set observed
    41      ///         after execution.
    42      EnumerableSet.AddressSet internal ownersBefore;
    43  
    44      /// @notice Constructor.
    45      /// @param _safe The safe account for which this contract will be the guard.
    46      constructor(Safe _safe) {
    47          SAFE = _safe;
    48          address[] memory owners = _safe.getOwners();
    49          for (uint256 i = 0; i < owners.length; i++) {
    50              address owner = owners[i];
    51              lastLive[owner] = block.timestamp;
    52              emit OwnerRecorded(owner);
    53          }
    54      }
    55  
    56      /// @notice Getter function for the Safe contract instance
    57      /// @return safe_ The Safe contract instance
    58      function safe() public view returns (Safe safe_) {
    59          safe_ = SAFE;
    60      }
    61  
    62      /// @notice Internal function to ensure that only the Safe can call certain functions.
    63      function _requireOnlySafe() internal view {
    64          require(msg.sender == address(SAFE), "LivenessGuard: only Safe can call this function");
    65      }
    66  
    67      /// @notice Records the most recent time which any owner has signed a transaction.
    68      /// @dev Called by the Safe contract before execution of a transaction.
    69      function checkTransaction(
    70          address to,
    71          uint256 value,
    72          bytes memory data,
    73          Enum.Operation operation,
    74          uint256 safeTxGas,
    75          uint256 baseGas,
    76          uint256 gasPrice,
    77          address gasToken,
    78          address payable refundReceiver,
    79          bytes memory signatures,
    80          address msgSender
    81      )
    82          external
    83      {
    84          msgSender; // silence unused variable warning
    85          _requireOnlySafe();
    86  
    87          // Cache the set of owners prior to execution.
    88          // This will be used in the checkAfterExecution method.
    89          address[] memory owners = SAFE.getOwners();
    90          for (uint256 i = 0; i < owners.length; i++) {
    91              ownersBefore.add(owners[i]);
    92          }
    93  
    94          // This call will reenter to the Safe which is calling it. This is OK because it is only reading the
    95          // nonce, and using the getTransactionHash() method.
    96          bytes32 txHash = SAFE.getTransactionHash({
    97              to: to,
    98              value: value,
    99              data: data,
   100              operation: operation,
   101              safeTxGas: safeTxGas,
   102              baseGas: baseGas,
   103              gasPrice: gasPrice,
   104              gasToken: gasToken,
   105              refundReceiver: refundReceiver,
   106              _nonce: SAFE.nonce() - 1
   107          });
   108  
   109          uint256 threshold = SAFE.getThreshold();
   110          address[] memory signers =
   111              SafeSigners.getNSigners({ dataHash: txHash, signatures: signatures, requiredSignatures: threshold });
   112  
   113          for (uint256 i = 0; i < signers.length; i++) {
   114              lastLive[signers[i]] = block.timestamp;
   115              emit OwnerRecorded(signers[i]);
   116          }
   117      }
   118  
   119      /// @notice Update the lastLive mapping according to the set of owners before and after execution.
   120      /// @dev Called by the Safe contract after the execution of a transaction.
   121      ///      We use this post execution hook to compare the set of owners before and after.
   122      ///      If the set of owners has changed then we:
   123      ///      1. Add new owners to the lastLive mapping
   124      ///      2. Delete removed owners from the lastLive mapping
   125      function checkAfterExecution(bytes32, bool) external {
   126          _requireOnlySafe();
   127          // Get the current set of owners
   128          address[] memory ownersAfter = SAFE.getOwners();
   129  
   130          // Iterate over the current owners, and remove one at a time from the ownersBefore set.
   131          for (uint256 i = 0; i < ownersAfter.length; i++) {
   132              // If the value was present, remove() returns true.
   133              address ownerAfter = ownersAfter[i];
   134              if (ownersBefore.remove(ownerAfter) == false) {
   135                  // This address was not already an owner, add it to the lastLive mapping
   136                  lastLive[ownerAfter] = block.timestamp;
   137              }
   138          }
   139  
   140          // Now iterate over the remaining ownersBefore entries. Any remaining addresses are no longer an owner, so we
   141          // delete them from the lastLive mapping.
   142          // We cache the ownersBefore set before iterating over it, because the remove() method mutates the set.
   143          address[] memory ownersBeforeCache = ownersBefore.values();
   144          for (uint256 i = 0; i < ownersBeforeCache.length; i++) {
   145              address ownerBefore = ownersBeforeCache[i];
   146              delete lastLive[ownerBefore];
   147              ownersBefore.remove(ownerBefore);
   148          }
   149      }
   150  
   151      /// @notice Enables an owner to demonstrate liveness by calling this method directly.
   152      ///         This is useful for owners who have not recently signed a transaction via the Safe.
   153      function showLiveness() external {
   154          require(SAFE.isOwner(msg.sender), "LivenessGuard: only Safe owners may demonstrate liveness");
   155          lastLive[msg.sender] = block.timestamp;
   156  
   157          emit OwnerRecorded(msg.sender);
   158      }
   159  }