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 }