Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/HypernativeGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {Safe} from "@safe/contracts/Safe.sol";
import {Enum} from "@safe/contracts/libraries/Enum.sol";
import {BaseTransactionGuard, ITransactionGuard, GuardManager} from "@safe/contracts/base/GuardManager.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IGuardPolicyExtension} from "./IGuardPolicyExtension.sol";
/**
* @title HypernativeGuard
* @author Hypernative
* @notice A transaction guard for the Safe smart contract wallet that enforces transaction approval policies
* @dev Extends BaseTransactionGuard and implements AccessControl for role-based management
*/
contract HypernativeGuard is BaseTransactionGuard, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
error OnlySafe();
error OnlyKeeper();
error OnlyKeeperOrSafe();
error UnapprovedHash();
error TimelockNotTriggered();
error TimelockNotCompleted();
error PolicyExtensionNotValid();
error PolicyExtensionNotFound();
error PolicyExtensionAlreadyExists();
/// @notice Address of the Safe wallet this guard is attached to
/// @dev Immutable and set during contract deployment
address payable public immutable safeAddress;
/// @notice Role identifier for keeper accounts that can approve transactions
bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
/// @notice Hash used for revoking operations
bytes32 public immutable revokingHash;
/// @notice Hash for the timelock activation transaction
bytes32 public immutable activateTimelockHash;
/// @notice Hash for the timelock deactivation transaction
bytes32 public immutable disableTimelockHash;
/// @dev Timestamp when the timelock expires
uint256 internal timelockBlock;
/// @notice Whether the timelock sequence has been triggered
bool public isTimelockTriggered;
/// @notice Mapping of approved transaction hashes to their approval status
mapping(bytes32 txHash => bool) public approvedTxHashes;
/// @notice Mapping of approved nonce-free transaction hashes to their approval status
mapping(bytes32 nonceFreeTxHash => bool) public approvedNonceFreeTxHashes;
/// @notice Mapping of approved function call hashes to their approval status
mapping(bytes32 functionCallTxHash => bool) public approvedFunctionCallHashes;
/// @dev Array of policy extension contract addresses that provide additional validation logic
EnumerableSet.AddressSet internal policyExtensions;
/**
* @notice Types of transaction hashes that can be approved
* @dev Used in events to indicate which type of hash is being approved or revoked
*/
enum HashType {
Regular,
NonceFree,
FunctionCall
}
/**
* @notice Emitted when the timelock is activated
* @param timestamp The timestamp when the timelock was activated
*/
event TimelockActivated(uint256 timestamp);
/**
* @notice Emitted when the timelock is disabled
* @param timestamp The timestamp when the timelock was disabled
*/
event TimelockDisabled(uint256 timestamp);
/**
* @notice Emitted when a hash is approved
* @param hash The hash that was approved
* @param hashType The type of the hash that was approved
*/
event HashApproved(bytes32 hash, HashType hashType);
/**
* @notice Emitted when a hash is revoked
* @param hash The hash that was revoked
* @param hashType The type of the hash that was revoked
*/
event HashRevoked(bytes32 hash, HashType hashType);
/**
* @notice Emitted when a policy extension is added
* @param policyExtension The address of the added policy extension
*/
event PolicyExtensionAdded(address policyExtension);
/**
* @notice Emitted when a policy extension is removed
* @param policyExtension The address of the removed policy extension
*/
event PolicyExtensionRemoved(address policyExtension);
/**
* @dev Restricts function access to accounts with the keeper role
*/
modifier onlyKeeper() {
require(hasRole(KEEPER_ROLE, msg.sender), OnlyKeeper());
_;
}
/**
* @dev Restricts function access to the guarded Safe contract
*/
modifier onlyGuardedSafe() {
require(msg.sender == safeAddress, OnlySafe());
_;
}
/**
* @dev Restricts function access to the keeper or the protected Safe contract
*/
modifier onlyKeeperOrSafe() {
require(hasRole(KEEPER_ROLE, msg.sender) || msg.sender == safeAddress, OnlyKeeperOrSafe());
_;
}
/**
* @notice Creates a new HypernativeGuard instance
* @dev Sets up initial configurations, approves timelock transactions, and assigns the deployer as keeper
* @param _safeAddress The address of the Safe this guard will protect
* @param _revokingHash The hash that identifies the HypernativeGuard revocation operations
*/
constructor(address payable _safeAddress, bytes32 _revokingHash, address _keeper) {
_grantRole(KEEPER_ROLE, _keeper);
safeAddress = _safeAddress;
revokingHash = _revokingHash;
activateTimelockHash = keccak256(
abi.encode(
address(this),
0,
keccak256(abi.encodeWithSelector(this.activateTimelock.selector)),
Enum.Operation.Call,
0,
0,
0,
address(0),
payable(0)
)
);
disableTimelockHash = keccak256(
abi.encode(
address(this),
0,
keccak256(abi.encodeWithSelector(this.disableTimelock.selector)),
Enum.Operation.Call,
0,
0,
0,
address(0),
payable(0)
)
);
// pre-approve timelock transaction hashes as nonce-free
approvedNonceFreeTxHashes[activateTimelockHash] = true;
approvedNonceFreeTxHashes[disableTimelockHash] = true;
}
/**
* @notice Validates transactions before execution by the Safe
* @dev Applies policy extensions and checks various hash approval methods
* @param to Destination address of the transaction
* @param value Ether value of the transaction
* @param data Transaction data payload
* @param operation Operation type (Call or DelegateCall)
* @param safeTxGas Gas that should be used for the safe transaction
* @param baseGas Gas costs for data used to trigger the safe transaction
* @param gasPrice Maximum gas price that should be used for this transaction
* @param gasToken Token address (or 0 if ETH) that is used for the payment
* @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin)
*/
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
// solhint-disable-next-line no-unused-vars
address payable refundReceiver,
bytes memory signatures,
address executor
) external view override onlyGuardedSafe {
// process policy extensions
for (uint256 i = 0; i < policyExtensions.length(); ++i) {
IGuardPolicyExtension(policyExtensions.at(i)).checkPolicy(
to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, signatures, executor
);
}
Safe safe = Safe(safeAddress);
bytes32 txHash = safe.getTransactionHash(
to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, safe.nonce() - 1
);
bytes32 nonceFreeTxHash = getNonceFreeTransactionHash(
to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver
);
bytes32 functionCallTxHash =
getFunctionCallHash(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver);
// if the transaction is a Guard change or revoke operation, check timelock status
// the revokingHash was set during contract deployment and is used to identify these operations
if (functionCallTxHash == revokingHash) {
require(timelockBlock > 0 && isTimelockTriggered, TimelockNotTriggered());
require(block.timestamp > timelockBlock, TimelockNotCompleted());
return;
} else if (
approvedTxHashes[txHash] || approvedNonceFreeTxHashes[nonceFreeTxHash]
|| approvedFunctionCallHashes[functionCallTxHash]
) {
return;
} else {
// if the transaction hash is not approved, revert
revert UnapprovedHash();
}
}
/**
* @notice Required by the ITransactionGuard interface, called after transaction execution
* @dev This function is a no-op in the current implementation
*/
function checkAfterExecution(bytes32, bool) external override {}
/**
* @notice Approves a transaction hash
* @dev Sets the approval status to true for a transaction hash
* @param txHash The hash of the transaction to approve
*/
function approveHash(bytes32 txHash) public onlyKeeper {
approvedTxHashes[txHash] = true;
emit HashApproved(txHash, HashType.Regular);
}
/**
* @notice Approves a nonce-free transaction hash
* @dev Sets the approval status to true for a nonce-free transaction hash
* @param nonceFreeTxHash The nonce-free hash of the transaction to approve
*/
function approveNonceFreeHash(bytes32 nonceFreeTxHash) public onlyKeeper {
approvedNonceFreeTxHashes[nonceFreeTxHash] = true;
emit HashApproved(nonceFreeTxHash, HashType.NonceFree);
}
/**
* @notice Approves a function call hash
* @dev Sets the approval status to true for a function call hash
* @param functionCallTxHash The function call hash to approve
*/
function approveFunctionCallHash(bytes32 functionCallTxHash) public onlyKeeper {
approvedFunctionCallHashes[functionCallTxHash] = true;
emit HashApproved(functionCallTxHash, HashType.FunctionCall);
}
/**
* @notice Revokes approval for a transaction hash
* @dev Sets the approval status to false for a transaction hash
* @param txHash The hash of the transaction to revoke approval for
*/
function revokeHash(bytes32 txHash) public onlyKeeper {
approvedTxHashes[txHash] = false;
emit HashRevoked(txHash, HashType.Regular);
}
/**
* @notice Revokes approval for a nonce-free transaction hash
* @dev Sets the approval status to false for a nonce-free transaction hash
* @param nonceFreeTxHash The nonce-free hash to revoke approval for
*/
function revokeNonceFreeHash(bytes32 nonceFreeTxHash) public onlyKeeper {
approvedNonceFreeTxHashes[nonceFreeTxHash] = false;
emit HashRevoked(nonceFreeTxHash, HashType.NonceFree);
}
/**
* @notice Revokes approval for a function call hash
* @dev Sets the approval status to false for a function call hash
* @param functionCallTxHash The function call hash to revoke approval for
*/
function revokeFunctionCallHash(bytes32 functionCallTxHash) public onlyKeeper {
approvedFunctionCallHashes[functionCallTxHash] = false;
emit HashRevoked(functionCallTxHash, HashType.FunctionCall);
}
/**
* @notice Adds a policy extension to the guard
* @dev Can only be called by the Safe contract
* @param _policyExtension Address of the policy extension to add
*/
function addPolicyExtension(address _policyExtension) public onlyGuardedSafe {
require(
IGuardPolicyExtension(_policyExtension).supportsInterface(type(IGuardPolicyExtension).interfaceId),
PolicyExtensionNotValid()
);
require(policyExtensions.add(_policyExtension), PolicyExtensionAlreadyExists());
emit PolicyExtensionAdded(_policyExtension);
}
/**
* @notice Removes a policy extension from the guard
* @param _policyExtension Address of the policy extension to remove
*/
function removePolicyExtension(address _policyExtension) public onlyKeeperOrSafe {
require(policyExtensions.remove(_policyExtension), PolicyExtensionNotFound());
emit PolicyExtensionRemoved(_policyExtension);
}
/**
* @notice Activates the timelock sequence
* @dev Sets the timelock expiration time to 1 day from the current block timestamp
*/
function activateTimelock() public onlyGuardedSafe {
isTimelockTriggered = true;
timelockBlock = block.timestamp + 1 days;
emit TimelockActivated(block.timestamp);
}
/**
* @notice Disables the timelock sequence
* @dev Can only be called by the Safe contract
*/
function disableTimelock() public onlyGuardedSafe {
isTimelockTriggered = false;
timelockBlock = 0;
emit TimelockDisabled(block.timestamp);
}
/**
* @notice Grants the keeper role to an address
* @dev Can only be called by the Safe contract
* @param _keeper Address to grant the keeper role to
*/
function grantKeeperRole(address _keeper) public onlyGuardedSafe {
_grantRole(KEEPER_ROLE, _keeper);
}
/**
* @notice Revokes the keeper role from an address
* @dev Can only be called by the Safe contract
* @param _keeper Address to revoke the keeper role from
*/
function revokeKeeperRole(address _keeper) public onlyGuardedSafe {
_revokeRole(KEEPER_ROLE, _keeper);
}
/**
* @notice Returns the timestamp when the timelock expires
* @dev Returns 0 if the timelock is not currently triggered
* @return The timestamp of the timelock expiration, or 0 if inactive
*/
function getTimelockBlock() public view returns (uint256) {
return isTimelockTriggered ? timelockBlock : 0;
}
/**
* @notice Gets all policy extensions
* @return Array of policy extension addresses
*/
function getPolicyExtensions() public view returns (address[] memory) {
return policyExtensions.values();
}
/**
* @notice Computes a nonce-free transaction hash
* @dev Hash is based on transaction parameters without considering the nonce
* @param to Destination address
* @param value Ether value of the transaction
* @param data Transaction data payload
* @param operation Operation type (Call or DelegateCall)
* @param safeTxGas Gas that should be used for the safe transaction
* @param baseGas Gas costs for data used to trigger the safe transaction
* @param gasPrice Maximum gas price that should be used for this transaction
* @param gasToken Token address (or 0 if ETH) that is used for the payment
* @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin)
* @return Hash of the transaction parameters without nonce
*/
function getNonceFreeTransactionHash(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver
) public pure returns (bytes32) {
return keccak256(
abi.encode(to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver)
);
}
/**
* @notice Computes a function call hash based on the function selector
* @dev Extracts the first 4 bytes of the data parameter (function selector)
* @param to Destination address
* @param value Ether value of the transaction
* @param data Transaction data payload
* @param operation Operation type (Call or DelegateCall)
* @param safeTxGas Gas that should be used for the safe transaction
* @param baseGas Gas costs for data used to trigger the safe transaction
* @param gasPrice Maximum gas price that should be used for this transaction
* @param gasToken Token address (or 0 if ETH) that is used for the payment
* @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin)
* @return Hash based on the function selector and transaction parameters
*/
function getFunctionCallHash(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver
) public pure returns (bytes32) {
bytes memory functionSelector = new bytes(4);
assembly {
let selectorData := mload(add(data, 0x20))
mstore(add(functionSelector, 0x20), selectorData)
}
return keccak256(
abi.encode(
to,
value,
keccak256(functionSelector),
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver
)
);
}
/**
* @notice Checks if this contract supports a given interface
* @dev Overrides implementation from both parent contracts
* @param interfaceId Interface identifier (4 bytes)
* @return True if the interface is supported
*/
function supportsInterface(bytes4 interfaceId)
public
view
override(BaseTransactionGuard, AccessControl)
returns (bool)
{
return interfaceId == type(ITransactionGuard).interfaceId // Safe Guard interface
|| AccessControl.supportsInterface(interfaceId);
}
}
"
},
"lib/safe-contracts/contracts/Safe.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
import {FallbackManager} from "./base/FallbackManager.sol";
import {ITransactionGuard, GuardManager} from "./base/GuardManager.sol";
import {ModuleManager} from "./base/ModuleManager.sol";
import {OwnerManager} from "./base/OwnerManager.sol";
import {NativeCurrencyPaymentFallback} from "./common/NativeCurrencyPaymentFallback.sol";
import {SecuredTokenTransfer} from "./common/SecuredTokenTransfer.sol";
import {SignatureDecoder} from "./common/SignatureDecoder.sol";
import {Singleton} from "./common/Singleton.sol";
import {StorageAccessible} from "./common/StorageAccessible.sol";
import {SafeMath} from "./external/SafeMath.sol";
import {ISafe} from "./interfaces/ISafe.sol";
import {ISignatureValidator, ISignatureValidatorConstants} from "./interfaces/ISignatureValidator.sol";
import {Enum} from "./libraries/Enum.sol";
/**
* @title Safe - A multisignature wallet with support for confirmations using signed messages based on EIP-712.
* @dev Most important concepts:
* - Threshold: Number of required confirmations for a Safe transaction.
* - Owners: List of addresses that control the Safe. They are the only ones that can add/remove owners, change the threshold and
* approve transactions. Managed in `OwnerManager`.
* - Transaction Hash: Hash of a transaction is calculated using the EIP-712 typed structured data hashing scheme.
* - Nonce: Each transaction should have a different nonce to prevent replay attacks.
* - Signature: A valid signature of an owner of the Safe for a transaction hash.
* - Guards: Guards are contracts that can execute pre- and post- transaction checks. There are two types of guards:
* 1. Transaction Guard: managed in `GuardManager` for transactions executed with `execTransaction`.
* 2. Module Guard: managed in `ModuleManager` for transactions executed with `execTransactionFromModule`
* - Modules: Modules are contracts that can be used to extend the write functionality of a Safe. Managed in `ModuleManager`.
* - Fallback: Fallback handler is a contract that can provide additional functionality for Safe. Managed in `FallbackManager`. Please read the security risks in the `IFallbackManager` interface.
* Note: This version of the implementation contract doesn't emit events for the sake of gas efficiency and therefore requires a tracing node for indexing/
* For the events-based implementation see `SafeL2.sol`.
* @author Stefan George - @Georgi87
* @author Richard Meissner - @rmeissner
*/
contract Safe is
Singleton,
NativeCurrencyPaymentFallback,
ModuleManager,
GuardManager,
OwnerManager,
SignatureDecoder,
SecuredTokenTransfer,
ISignatureValidatorConstants,
FallbackManager,
StorageAccessible,
ISafe
{
using SafeMath for uint256;
string public constant override VERSION = "1.5.0";
// keccak256(
// "EIP712Domain(uint256 chainId,address verifyingContract)"
// );
bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;
// keccak256(
// "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"
// );
bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8;
uint256 public override nonce;
bytes32 private _deprecatedDomainSeparator;
// Mapping to keep track of all message hashes that have been approved by ALL REQUIRED owners
mapping(bytes32 => uint256) public override signedMessages;
// Mapping to keep track of all hashes (message or transaction) that have been approved by ANY owners
mapping(address => mapping(bytes32 => uint256)) public override approvedHashes;
// This constructor ensures that this contract can only be used as a singleton for Proxy contracts
constructor() {
/**
* By setting the threshold it is not possible to call setup anymore,
* so we create a Safe with 0 owners and threshold 1.
* This is an unusable Safe, perfect for the singleton
*/
threshold = 1;
}
/**
* @inheritdoc ISafe
*/
function setup(
address[] calldata _owners,
uint256 _threshold,
address to,
bytes calldata data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address payable paymentReceiver
) external override {
// setupOwners checks if the Threshold is already set, therefore preventing this method from being called more than once
setupOwners(_owners, _threshold);
if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler);
// As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules
setupModules(to, data);
if (payment > 0) {
// To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code that has been verified we do not adjust the method itself)
// baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment
handlePayment(payment, 0, 1, paymentToken, paymentReceiver);
}
emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler);
}
/**
* @inheritdoc ISafe
*/
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) external payable override returns (bool success) {
onBeforeExecTransaction(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, signatures);
bytes32 txHash;
// Use scope here to limit variable lifetime and prevent `stack too deep` errors
{
txHash = getTransactionHash( // Transaction info
to,
value,
data,
operation,
safeTxGas,
// Payment info
baseGas,
gasPrice,
gasToken,
refundReceiver,
// Signature info
// We use the post-increment here, so the current nonce value is used and incremented afterwards.
nonce++
);
checkSignatures(msg.sender, txHash, signatures);
}
address guard = getGuard();
{
if (guard != address(0)) {
ITransactionGuard(guard).checkTransaction(
// Transaction info
to,
value,
data,
operation,
safeTxGas,
// Payment info
baseGas,
gasPrice,
gasToken,
refundReceiver,
// Signature info
signatures,
msg.sender
);
}
}
// We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)
// We also include the 1/64 in the check that is not sent along with a call to counteract potential shortings because of EIP-150
// We use `<< 6` instead of `* 64` as SHR / SHL opcode only uses 3 gas, while DIV / MUL opcode uses 5 gas.
if (gasleft() < ((safeTxGas << 6) / 63).max(safeTxGas + 2500) + 500) revertWithError("GS010");
// Use scope here to limit variable lifetime and prevent `stack too deep` errors
{
uint256 gasUsed = gasleft();
// If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas)
// We only subtract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas
success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas);
gasUsed = gasUsed.sub(gasleft());
// If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful
// This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert
if (!success && safeTxGas == 0 && gasPrice == 0) {
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
/* solhint-enable no-inline-assembly */
}
// We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls
uint256 payment = 0;
if (gasPrice > 0) {
payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver);
}
if (success) emit ExecutionSuccess(txHash, payment);
else emit ExecutionFailure(txHash, payment);
}
{
if (guard != address(0)) {
ITransactionGuard(guard).checkAfterExecution(txHash, success);
}
}
}
/**
* @notice Handles the payment for a Safe transaction.
* @param gasUsed Gas used by the Safe transaction.
* @param baseGas Gas costs that are independent of the transaction execution (e.g. base transaction fee, signature check, payment of the refund).
* @param gasPrice Gas price that should be used for the payment calculation.
* @param gasToken Token address (or 0 if ETH) that is used for the payment.
* @return payment The amount of payment made in the specified token.
*/
function handlePayment(
uint256 gasUsed,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver
) private returns (uint256 payment) {
// solhint-disable-next-line avoid-tx-origin
address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver;
if (gasToken == address(0)) {
// For native tokens, we will only adjust the gas price to not be higher than the actually used gas price
payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice);
(bool refundSuccess, ) = receiver.call{value: payment}("");
if (!refundSuccess) revertWithError("GS011");
} else {
payment = gasUsed.add(baseGas).mul(gasPrice);
if (!transferToken(gasToken, receiver, payment)) revertWithError("GS012");
}
}
/**
* @notice Checks whether the contract signature is valid. Reverts otherwise.
* @dev This is extracted to a separate function for better compatibility with Certora's prover.
* More info here: https://github.com/safe-global/safe-smart-account/pull/661
* @param owner Address of the owner used to sign the message
* @param dataHash Hash of the data (could be either a message hash or transaction hash)
* @param signatures Signature data that should be verified.
* @param offset Offset to the start of the contract signature in the signatures byte array
*/
function checkContractSignature(address owner, bytes32 dataHash, bytes memory signatures, uint256 offset) internal view {
// Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes)
if (offset.add(32) > signatures.length) revertWithError("GS022");
// Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length
uint256 contractSignatureLen;
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
contractSignatureLen := mload(add(add(signatures, offset), 0x20))
}
/* solhint-enable no-inline-assembly */
if (offset.add(32).add(contractSignatureLen) > signatures.length) revertWithError("GS023");
// Check signature
bytes memory contractSignature;
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
// The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s
contractSignature := add(add(signatures, offset), 0x20)
}
/* solhint-enable no-inline-assembly */
if (ISignatureValidator(owner).isValidSignature(dataHash, contractSignature) != EIP1271_MAGIC_VALUE) revertWithError("GS024");
}
/**
* @inheritdoc ISafe
*/
function checkSignatures(address executor, bytes32 dataHash, bytes memory signatures) public view override {
// Load threshold to avoid multiple storage loads
uint256 _threshold = threshold;
// Check that a threshold is set
if (_threshold == 0) revertWithError("GS001");
checkNSignatures(executor, dataHash, signatures, _threshold);
}
/**
* @inheritdoc ISafe
*/
function checkNSignatures(
address executor,
bytes32 dataHash,
bytes memory signatures,
uint256 requiredSignatures
) public view override {
// Check that the provided signature data is not too short
if (signatures.length < requiredSignatures.mul(65)) revertWithError("GS020");
// There cannot be an owner with address 0.
address lastOwner = address(0);
address currentOwner;
uint256 v; // Implicit conversion from uint8 to uint256 will be done for v received from signatureSplit(...).
bytes32 r;
// NOTE: We do not enforce the `s` to be from the lower half of the curve
// This essentially means that for every signature, there's another valid signature (known as ECDSA malleability)
// Since we have other mechanisms to prevent duplicated signatures (ordered owners array) and replay protection (nonce),
// we can safely ignore this malleability.
bytes32 s;
uint256 i;
for (i = 0; i < requiredSignatures; ++i) {
(v, r, s) = signatureSplit(signatures, i);
if (v == 0) {
// If v is 0 then it is a contract signature
// When handling contract signatures the address of the contract is encoded into r
currentOwner = address(uint160(uint256(r)));
// Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes
// This check is not completely accurate, since it is possible that more signatures than the threshold are sent.
// Here we only check that the pointer is not pointing inside the part that is being processed
if (uint256(s) < requiredSignatures.mul(65)) revertWithError("GS021");
// The contract signature check is extracted to a separate function for better compatibility with formal verification
// A quote from the Certora team:
// "The assembly code broke the pointer analysis, which switched the prover in failsafe mode, where it is (a) much slower and (b) computes different hashes than in the normal mode."
// More info here: https://github.com/safe-global/safe-smart-account/pull/661
checkContractSignature(currentOwner, dataHash, signatures, uint256(s));
} else if (v == 1) {
// If v is 1 then it is an approved hash
// When handling approved hashes the address of the approver is encoded into r
currentOwner = address(uint160(uint256(r)));
// Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction
if (executor != currentOwner && approvedHashes[currentOwner][dataHash] == 0) revertWithError("GS025");
} else if (v > 30) {
// If v > 30 then default va (27,28) has been adjusted for eth_sign flow
// To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover
currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\
32", dataHash)), uint8(v - 4), r, s);
} else {
// Default is the ecrecover flow with the provided data hash
// Use ecrecover with the messageHash for EOA signatures
currentOwner = ecrecover(dataHash, uint8(v), r, s);
}
if (currentOwner <= lastOwner || owners[currentOwner] == address(0) || currentOwner == SENTINEL_OWNERS)
revertWithError("GS026");
lastOwner = currentOwner;
}
}
/**
* @notice Checks whether the signature provided is valid for the provided hash. Reverts otherwise.
* The `data` parameter is completely ignored during signature verification.
* @dev This function is provided for compatibility with previous versions.
* Use `checkSignatures(address,bytes32,bytes)` instead.
* ⚠️⚠️⚠️ If the caller is an owner of the Safe, it can trivially sign any hash with a pre-approve signature and may reduce the threshold of the signature by 1. ⚠️⚠️⚠️
* @param dataHash Hash of the data (could be either a message hash or transaction hash).
* @param data **IGNORED** The data pre-image.
* @param signatures Signature data that should be verified.
* Can be packed ECDSA signature ({bytes32 r}{bytes32 s}{uint8 v}), contract signature (EIP-1271) or approved hash.
*/
function checkSignatures(bytes32 dataHash, bytes calldata data, bytes memory signatures) external view {
data;
checkSignatures(msg.sender, dataHash, signatures);
}
/**
* @notice Checks whether the signature provided is valid for the provided hash. Reverts otherwise.
* The `data` parameter is completely ignored during signature verification.
* @dev This function is provided for compatibility with previous versions.
* Use `checkNSignatures(address,bytes32,bytes,uint256)` instead.
* ⚠️⚠️⚠️ If the caller is an owner of the Safe, it can trivially sign any hash with a pre-approve signature and may reduce the threshold of the signature by 1. ⚠️⚠️⚠️
* @param dataHash Hash of the data (could be either a message hash or transaction hash)
* @param data **IGNORED** The data pre-image.
* @param signatures Signature data that should be verified.
* Can be packed ECDSA signature ({bytes32 r}{bytes32 s}{uint8 v}), contract signature (EIP-1271) or approved hash.
* @param requiredSignatures Amount of required valid signatures.
*/
function checkNSignatures(bytes32 dataHash, bytes calldata data, bytes memory signatures, uint256 requiredSignatures) external view {
data;
checkNSignatures(msg.sender, dataHash, signatures, requiredSignatures);
}
/**
* @inheritdoc ISafe
*/
function approveHash(bytes32 hashToApprove) external override {
if (owners[msg.sender] == address(0)) revertWithError("GS030");
approvedHashes[msg.sender][hashToApprove] = 1;
emit ApproveHash(hashToApprove, msg.sender);
}
/**
* @inheritdoc ISafe
*/
function domainSeparator() public view override returns (bytes32) {
uint256 chainId;
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
chainId := chainid()
}
/* solhint-enable no-inline-assembly */
return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, chainId, this));
}
/**
* @inheritdoc ISafe
*/
function getTransactionHash(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) public view override returns (bytes32 txHash) {
bytes32 domainHash = domainSeparator();
// We opted for using assembly code here, because the way Solidity compiler we use (0.7.6) allocates memory is
// inefficient. We do not need to allocate memory for temporary variables to be used in the keccak256 call.
//
// WARNING: We do not clean potential dirty bits in types that are less than 256 bits (addresses and Enum.Operation)
// The solidity assembly types that are smaller than 256 bit can have dirty high bits according to the spec (see the Warning in https://docs.soliditylang.org/en/latest/assembly.html#access-to-external-variables-functions-and-libraries).
// However, we read most of the data from calldata, where the variables are not packed, and the only variable we read from storage is uint256 nonce.
// This is not a problem, however, we must consider this for potential future changes.
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
// Get the free memory pointer
let ptr := mload(0x40)
// Step 1: Hash the transaction data
// Copy transaction data to memory and hash it
calldatacopy(ptr, data.offset, data.length)
let calldataHash := keccak256(ptr, data.length)
// Step 2: Prepare the SafeTX struct for hashing
// Layout in memory:
// ptr + 0: SAFE_TX_TYPEHASH (constant defining the struct hash)
// ptr + 32: to address
// ptr + 64: value
// ptr + 96: calldataHash
// ptr + 128: operation
// ptr + 160: safeTxGas
// ptr + 192: baseGas
// ptr + 224: gasPrice
// ptr + 256: gasToken
// ptr + 288: refundReceiver
// ptr + 320: nonce
mstore(ptr, SAFE_TX_TYPEHASH)
mstore(add(ptr, 32), to)
mstore(add(ptr, 64), value)
mstore(add(ptr, 96), calldataHash)
mstore(add(ptr, 128), operation)
mstore(add(ptr, 160), safeTxGas)
mstore(add(ptr, 192), baseGas)
mstore(add(ptr, 224), gasPrice)
mstore(add(ptr, 256), gasToken)
mstore(add(ptr, 288), refundReceiver)
mstore(add(ptr, 320), _nonce)
// Step 3: Calculate the final EIP-712 hash
// First, hash the SafeTX struct (352 bytes total length)
mstore(add(ptr, 64), keccak256(ptr, 352))
// Store the EIP-712 prefix (0x1901), note that integers are left-padded
// so the EIP-712 encoded data starts at add(ptr, 30)
mstore(ptr, 0x1901)
// Store the domain separator
mstore(add(ptr, 32), domainHash)
// Calculate the hash
txHash := keccak256(add(ptr, 30), 66)
}
/* solhint-enable no-inline-assembly */
}
/**
* @notice A hook that gets called before execution of {execTransaction} method.
* @param to Destination address of module transaction.
* @param value Ether value of module transaction.
* @param data Data payload of module transaction.
* @param operation Operation type of module transaction.
*/
function onBeforeExecTransaction(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) internal virtual {}
}
"
},
"lib/safe-contracts/contracts/libraries/Enum.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Enum - Collection of enums used in Safe Smart Account contracts.
* @author @safe-global/safe-protocol
*/
library Enum {
enum Operation {
Call,
DelegateCall
}
}
"
},
"lib/safe-contracts/contracts/base/GuardManager.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable one-contract-per-file */
pragma solidity >=0.7.0 <0.9.0;
import {SelfAuthorized} from "./../common/SelfAuthorized.sol";
import {IERC165} from "./../interfaces/IERC165.sol";
import {IGuardManager} from "./../interfaces/IGuardManager.sol";
import {Enum} from "./../libraries/Enum.sol";
/**
* @title ITransactionGuard Interface
*/
interface ITransactionGuard is IERC165 {
/**
* @notice Checks the transaction details.
* @dev The function needs to implement transaction validation logic.
* @param to The address to which the transaction is intended.
* @param value The value of the transaction in Wei.
* @param data The transaction data.
* @param operation The type of operation of the transaction.
* @param safeTxGas Gas used for the transaction.
* @param baseGas The base gas for the transaction.
* @param gasPrice The price of gas in Wei for the transaction.
* @param gasToken The token used to pay for gas.
* @param refundReceiver The address which should receive the refund.
* @param signatures The signatures of the transaction.
* @param msgSender The address of the message sender.
*/
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures,
address msgSender
) external;
/**
* @notice Checks after execution of the transaction.
* @dev The function needs to implement a check after the execution of the transaction.
* @param hash The hash of the transaction.
* @param success The status of the transaction execution.
*/
function checkAfterExecution(bytes32 hash, bool success) external;
}
abstract contract BaseTransactionGuard is ITransactionGuard {
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(ITransactionGuard).interfaceId || // 0xe6d7a83a
interfaceId == type(IERC165).interfaceId; // 0x01ffc9a7
}
}
/**
* @title Guard Manager - A contract managing transaction guards which perform pre and post-checks on Safe transactions.
* @author Richard Meissner - @rmeissner
*/
abstract contract GuardManager is SelfAuthorized, IGuardManager {
// keccak256("guard_manager.guard.address")
bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;
/**
* @inheritdoc IGuardManager
*/
function setGuard(address guard) external override authorized {
if (guard != address(0) && !ITransactionGuard(guard).supportsInterface(type(ITransactionGuard).interfaceId))
revertWithError("GS300");
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
sstore(GUARD_STORAGE_SLOT, guard)
}
/* solhint-enable no-inline-assembly */
emit ChangedGuard(guard);
}
/**
* @dev Internal method to retrieve the current guard
* We do not have a public method because we're short on bytecode size limit,
* to retrieve the guard address, one can use `getStorageAt` from `StorageAccessible` contract
* with the slot `GUARD_STORAGE_SLOT`
* @return guard The address of the guard
*/
function getGuard() internal view returns (address guard) {
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
guard := sload(GUARD_STORAGE_SLOT)
}
/* solhint-enable no-inline-assembly */
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
import {Arrays} from "../Arrays.sol";
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
* - Set can be cleared (all elements removed) in O(n).
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function _clear(Set storage set) private {
uint256 len = _length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(Bytes32Set storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
Submitted on: 2025-10-19 17:08:54
Comments
Log in to comment.
No comments yet.