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/Facets/ExecutorFacet.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.24;
import {MerkleProof} from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
import {LibDiamond} from "../Libraries/LibDiamond.sol";
import {IExecutorTypes} from "../Interfaces/IExecutorTypes.sol";
import {IAddressProviderService} from "../Interfaces/IAddressProviderService.sol";
import {IOwnershipFacet} from "../Interfaces/IOwnershipFacet.sol";
import {BigMathMinified} from "../Libraries/bigMathMinified.sol";
import {ThyraRegistry} from "../ThyraRegistry.sol";
import {IModuleManager} from "safe-smart-account/contracts/interfaces/IModuleManager.sol";
import {Enum} from "safe-smart-account/contracts/libraries/Enum.sol";
/// @title ExecutorFacet
/// @author Thyra.fi
/// @notice Facet for executing transactions through Merkle tree-based task system
/// @dev This Facet allows contract owners to register tasks and authorized executors to execute operations with Merkle proofs
/// @custom:version 3.0.0
contract ExecutorFacet is IExecutorTypes, IAddressProviderService {
/// Storage ///
bytes32 internal constant NAMESPACE = keccak256("io.thyra.facets.executor");
/// @notice ThyraRegistry address for all ExecutorFacet instances
/// @dev This address is set once during deployment and shared by all Diamond instances
address public immutable THYRA_REGISTRY;
/// @notice Constructor to set the ThyraRegistry address
/// @param _thyraRegistry Address of the ThyraRegistry contract
constructor(address _thyraRegistry) {
THYRA_REGISTRY = _thyraRegistry;
}
/// Types ///
/// @notice Optimized Task structure using only 2 storage slots
/// @dev Slot 1: executor (20 bytes) + executedOperationsBitmap (11 bytes) + status (1 byte)
/// Slot 2: feeToken (20 bytes) + initFee (6 bytes) + maxFee (6 bytes)
/// Note: merkleRoot is now the mapping key, no longer stored in struct
struct Task {
// --- Slot 1: 32 bytes (Packed) ---
uint256 slot1; // Contains: executor(160 bits) + executedOperationsBitmap(88 bits) + status(8 bits)
// --- Slot 2: 32 bytes (Packed) ---
uint256 slot2; // Contains: feeToken(160 bits) + initFee(48 bits) + maxFee(48 bits)
}
struct Storage {
/// @notice Task registry mapping from Merkle root to task details
mapping(bytes32 merkleRoot => Task) tasks;
}
/// Errors ///
error TaskNotFound();
error UnauthorizedExecutor();
error TaskNotActive();
error InvalidMerkleProof();
error OperationAlreadyExecuted();
error OperationNotRepeatable();
error InvalidTimeWindow();
error GasPriceTooHigh();
error ExecutionFailed();
error InvalidCallType();
error InvalidTaskStatus();
error OperationIdOutOfBounds();
error ModuleExecutionFailed();
error TaskAlreadyRegistered();
/// Events ///
event TaskRegistered(
bytes32 indexed merkleRoot, address indexed executor, address feeToken, uint256 initFee, uint256 maxFee
);
event TaskStatusChanged(bytes32 indexed merkleRoot, TaskStatus oldStatus, TaskStatus newStatus);
event ExecutionSuccess(
bytes32 indexed merkleRoot,
address indexed executor,
address indexed target,
uint256 value,
bytes data,
CallType callType
);
/// External Methods ///
/**
* @notice Register a new task with operations Merkle tree and activate it immediately
* @dev Integrates with ThyraRegistry for global validation of executors and fee tokens
* @param _merkleRoot Merkle root of the task operations tree
* @param _executor Authorized executor address for this task
* @param _feeToken ERC20 token address for fee payment
* @param _initFee Initial fee amount in the token's native decimals
* @param _maxFee Maximum fee amount in the token's native decimals
*/
function registerTask(bytes32 _merkleRoot, address _executor, address _feeToken, uint96 _initFee, uint96 _maxFee)
external
{
// Must be called by contract owner
LibDiamond.enforceIsContractOwner();
Storage storage s = getStorage();
Task storage task = s.tasks[_merkleRoot];
// Check if task already exists
if (task.slot1 != 0) {
revert TaskAlreadyRegistered();
}
// Validate with hardcoded ThyraRegistry using centralized validation
ThyraRegistry registry = ThyraRegistry(THYRA_REGISTRY);
// Single call to validate all task registration parameters
registry.validateTaskRegistration(_executor, _feeToken, _initFee, _maxFee);
// Convert fees to BigNumber format (48-bit each)
uint256 initFeeBigNum = BigMathMinified.toBigNumber(_initFee, 40, 8, false);
uint256 maxFeeBigNum = BigMathMinified.toBigNumber(_maxFee, 40, 8, false);
// Slot1 layout: executor(160 bits) | bitmap(88 bits) | status(8 bits)
task.slot1 = uint256(uint8(TaskStatus.ACTIVE)) | (0 << 8) // bitmap starts empty (88 bits)
| (uint256(uint160(_executor)) << 96);
// Slot2 layout: feeToken(160 bits) | initFee(48 bits) | maxFee(48 bits)
task.slot2 = maxFeeBigNum | (initFeeBigNum << 48) | (uint256(uint160(_feeToken)) << 96);
emit TaskRegistered(_merkleRoot, _executor, _feeToken, _initFee, _maxFee);
}
/**
* @notice Update task status with role-based permissions
* @dev Owner: can set non-completed tasks to CANCELLED
* Executor: can set ACTIVE tasks to COMPLETED or CANCELLED
* @param _merkleRoot Merkle root identifying the task
* @param _newStatus New status for the task
*/
function updateTaskStatus(bytes32 _merkleRoot, TaskStatus _newStatus) external {
Storage storage s = getStorage();
Task storage task = s.tasks[_merkleRoot];
// Check if task exists
if (task.slot1 == 0) {
revert TaskNotFound();
}
// Unpack required data for validation and events
uint256 slot1 = task.slot1;
address executor = address(uint160(slot1 >> 96));
TaskStatus oldStatus = TaskStatus(uint8(slot1 & 0xFF));
// Role-based permission checks
bool isOwner = (msg.sender == LibDiamond.contractOwner());
bool isExecutor = (msg.sender == executor);
if (
!(
(isOwner && _newStatus == TaskStatus.CANCELLED && oldStatus != TaskStatus.COMPLETED)
|| (
isExecutor && oldStatus == TaskStatus.ACTIVE
&& (_newStatus == TaskStatus.COMPLETED || _newStatus == TaskStatus.CANCELLED)
)
)
) {
if (isOwner || isExecutor) {
revert InvalidTaskStatus();
} else {
revert UnauthorizedExecutor();
}
}
// Update only the status bits (lowest 8 bits), keep other bits unchanged
task.slot1 = (slot1 & ~uint256(0xFF)) | uint256(uint8(_newStatus));
emit TaskStatusChanged(_merkleRoot, oldStatus, _newStatus);
}
/**
* @notice Execute a registered task operation through Merkle proof
* @dev Implements a clear 4-phase validation process before execution using memory snapshots
* @param _merkleRoot Merkle root identifying the task and validating the operation
* @param _operation Operation to execute (Merkle leaf)
* @param _merkleProof Merkle proof to validate the operation
* @return returnData Data returned by the executed transaction
*/
function executeTransaction(bytes32 _merkleRoot, Operation calldata _operation, bytes32[] calldata _merkleProof)
external
returns (bytes memory returnData)
{
Storage storage s = getStorage();
// Load task slot1 into memory for efficient access
uint256 slot1 = s.tasks[_merkleRoot].slot1;
// Phase 1: Task basic state validation
_validateTaskState(slot1);
// Phase 2: Cryptographic validation
_validateMerkleProof(_merkleRoot, _operation, _merkleProof);
// Phase 3: Operation internal constraints validation
_validateOperationConstraints(slot1, _operation);
// Validation passed, execute transaction
// Step 4: Execute operation
returnData = _executeOperation(_operation);
// Step 5: Update execution state (if needed)
_updateExecutionState(_merkleRoot, slot1, _operation.operationId, _operation.isRepeatable);
emit ExecutionSuccess(
_merkleRoot, msg.sender, _operation.target, _operation.value, _operation.callData, _operation.callType
);
return returnData;
}
/**
* @notice Get task information
* @param _merkleRoot Merkle root identifying the task
* @return executor Authorized executor address
* @return status Current task status
* @return feeToken Fee token address
* @return initFee Initial fee amount (in original decimals)
* @return maxFee Maximum fee amount (in original decimals)
*/
function getTaskInfo(bytes32 _merkleRoot)
external
view
returns (address executor, TaskStatus status, address feeToken, uint256 initFee, uint256 maxFee)
{
Storage storage s = getStorage();
Task storage task = s.tasks[_merkleRoot];
// Check if task exists
if (task.slot1 == 0) {
revert TaskNotFound();
}
// Unpack data from slots using direct bit operations
uint256 slot1 = task.slot1;
uint256 slot2 = task.slot2;
executor = address(uint160(slot1 >> 96));
status = TaskStatus(uint8(slot1 & 0xFF));
feeToken = address(uint160(slot2 >> 96));
initFee = (slot2 >> 48) & ((1 << 48) - 1);
maxFee = slot2 & ((1 << 48) - 1);
// Convert BigNumber fees back to normal numbers
if (initFee > 0) {
initFee = BigMathMinified.fromBigNumber(initFee, 8, 0xFF);
}
if (maxFee > 0) {
maxFee = BigMathMinified.fromBigNumber(maxFee, 8, 0xFF);
}
return (executor, status, feeToken, initFee, maxFee);
}
/**
* @notice Get the ThyraRegistry contract address
* @return The address of the ThyraRegistry contract
*/
function getThyraRegistry() external view returns (address) {
return THYRA_REGISTRY;
}
/**
* @notice Check if a specific operation has been executed
* @param _merkleRoot Merkle root identifying the task
* @param _operationId Operation ID to check (must be < 88 due to bitmap size constraint)
* @return executed Whether the operation has been executed
*/
function isOperationExecuted(bytes32 _merkleRoot, uint32 _operationId) external view returns (bool executed) {
if (_operationId >= 88) {
revert OperationIdOutOfBounds();
}
Storage storage s = getStorage();
Task storage task = s.tasks[_merkleRoot];
// Check if task exists
if (task.slot1 == 0) {
revert TaskNotFound();
}
uint256 bitmap = (task.slot1 >> 8) & ((1 << 88) - 1);
executed = (bitmap & (1 << _operationId)) != 0;
}
/// Internal Methods ///
/**
* @notice Phase 1: Validate task state and caller permissions
* @dev Checks task existence, executor authorization, and task active status
* @param _slot1 Task slot1 memory snapshot
*/
function _validateTaskState(uint256 _slot1) internal view {
// Check if task exists - fail fast
if (_slot1 == 0) {
revert TaskNotFound();
}
// Only the designated executor can execute operations
if (msg.sender != address(uint160(_slot1 >> 96))) {
revert UnauthorizedExecutor();
}
// Check if task is active
if (TaskStatus(uint8(_slot1 & 0xFF)) != TaskStatus.ACTIVE) {
revert TaskNotActive();
}
}
/**
* @notice Phase 2: Validate Merkle proof cryptographically
* @dev Ensures the operation is part of the merkle tree
* @param _merkleRoot Root of the merkle tree
* @param _operation Operation to validate
* @param _merkleProof Merkle proof for the operation
*/
function _validateMerkleProof(bytes32 _merkleRoot, Operation calldata _operation, bytes32[] calldata _merkleProof)
internal
pure
{
bytes32 leafHash = keccak256(abi.encode(_operation));
if (!MerkleProof.verify(_merkleProof, _merkleRoot, leafHash)) {
revert InvalidMerkleProof();
}
}
/**
* @notice Phase 3: Validate operation constraints and execution eligibility
* @dev Checks call type, operation ID bounds, time window, gas price, and repeatability
* @param _slot1 Task slot1 memory snapshot containing execution state
* @param _operation Operation to validate
*/
function _validateOperationConstraints(uint256 _slot1, Operation calldata _operation) internal view {
// Check operation ID bounds for our 88-bit bitmap - fail fast
if (_operation.operationId >= 88) {
revert OperationIdOutOfBounds();
}
// Check call type - we only support CALL, not DELEGATECALL
if (_operation.callType != CallType.CALL) {
revert InvalidCallType();
}
// Check time window
if (block.timestamp < _operation.startTime || block.timestamp > _operation.endTime) {
revert InvalidTimeWindow();
}
// Check gas price
if (tx.gasprice > _operation.maxGasPrice) {
revert GasPriceTooHigh();
}
// If operation is not repeatable, check execution history
if (!_operation.isRepeatable) {
uint256 bitmap = (_slot1 >> 8) & ((1 << 88) - 1);
if ((bitmap & (1 << _operation.operationId)) != 0) {
revert OperationAlreadyExecuted();
}
}
}
/**
* @notice Update memory snapshot with executed operation and write back to storage
* @dev Only updates for non-repeatable operations
* @param _merkleRoot Merkle root identifying the task
* @param _slot1 Current slot1 memory snapshot
* @param _operationId Operation ID to mark as executed
* @param _isRepeatable Whether the operation can be executed multiple times
*/
function _updateExecutionState(bytes32 _merkleRoot, uint256 _slot1, uint32 _operationId, bool _isRepeatable)
internal
{
if (!_isRepeatable) {
// Single OR operation to set the operation bit in the bitmap
Storage storage s = getStorage();
s.tasks[_merkleRoot].slot1 = _slot1 | (1 << (_operationId + 8));
}
}
/**
* @notice Execute operation through Safe wallet as module
* @param _operation Operation to execute
* @return returnData Data returned by execution
*/
function _executeOperation(Operation memory _operation) internal returns (bytes memory returnData) {
// Get Safe wallet address via OwnershipFacet interface
// CRITICAL: We cannot use assembly sload(0) because ReentrancyGuard's _status
// variable also uses slot 0, which would conflict in delegatecall context
address safeWallet = IOwnershipFacet(address(this)).safeWallet();
// Execute transaction through Safe as module
// CallType already validated in _validateOperationConstraints, we only support CALL
(bool success, bytes memory txnResult) = IModuleManager(safeWallet).execTransactionFromModuleReturnData(
_operation.target, _operation.value, _operation.callData, Enum.Operation.Call
);
if (!success) {
// Forward revert reason if available
if (txnResult.length > 0) {
// solhint-disable-next-line no-inline-assembly
assembly {
revert(add(32, txnResult), mload(txnResult))
}
} else {
revert ModuleExecutionFailed();
}
}
return txnResult;
}
/// @dev Fetch local storage using Diamond storage pattern
function getStorage() private pure returns (Storage storage s) {
bytes32 namespace = NAMESPACE;
// solhint-disable-next-line no-inline-assembly
assembly {
s.slot := namespace
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.
pragma solidity ^0.8.20;
import {Hashes} from "./Hashes.sol";
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*
* IMPORTANT: Consider memory side-effects when using custom hashing functions
* that access memory in an unsafe way.
*
* NOTE: This library supports proof verification for merkle trees built using
* custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
* leaf inclusion in trees built using non-commutative hashing functions requires
* additional logic that is not supported by this library.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProof(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function processProof(
bytes32[] memory proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProofCalldata(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function processProofCalldata(
bytes32[] calldata proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProof(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
}
"
},
"src/Libraries/LibDiamond.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;
import {LibDiamond} from "../Libraries/LibDiamond.sol";
import {LibUtil} from "../Libraries/LibUtil.sol";
import {OnlyContractOwner} from "../Errors/GenericErrors.sol";
/// @title LibDiamond
/// @custom:version 1.0.0
/// @notice This library implements the EIP-2535 Diamond Standard
library LibDiamond {
bytes32 internal constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
// Diamond specific errors
error IncorrectFacetCutAction();
error NoSelectorsInFace();
error FunctionAlreadyExists();
error FacetAddressIsZero();
error FacetAddressIsNotZero();
error FacetContainsNoCode();
error FunctionDoesNotExist();
error FunctionIsImmutable();
error InitZeroButCalldataNotEmpty();
error CalldataEmptyButInitNotZero();
error InitReverted();
// ----------------
struct FacetAddressAndPosition {
address facetAddress;
uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
}
struct FacetFunctionSelectors {
bytes4[] functionSelectors;
uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
}
struct DiamondStorage {
// maps function selector to the facet address and
// the position of the selector in the facetFunctionSelectors.selectors array
mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
// maps facet addresses to function selectors
mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
// facet addresses
address[] facetAddresses;
// Used to query if a contract implements an interface.
// Used to implement ERC-165.
mapping(bytes4 => bool) supportedInterfaces;
// owner of the contract
address contractOwner;
}
enum FacetCutAction {
Add,
Replace,
Remove
}
// Add=0, Replace=1, Remove=2
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
function diamondStorage() internal pure returns (DiamondStorage storage ds) {
bytes32 position = DIAMOND_STORAGE_POSITION;
// solhint-disable-next-line no-inline-assembly
assembly {
ds.slot := position
}
}
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
function setContractOwner(address _newOwner) internal {
DiamondStorage storage ds = diamondStorage();
address previousOwner = ds.contractOwner;
ds.contractOwner = _newOwner;
emit OwnershipTransferred(previousOwner, _newOwner);
}
function contractOwner() internal view returns (address contractOwner_) {
contractOwner_ = diamondStorage().contractOwner;
}
function enforceIsContractOwner() internal view {
if (msg.sender != diamondStorage().contractOwner) {
revert OnlyContractOwner();
}
}
// Internal function version of diamondCut
function diamondCut(FacetCut[] memory _diamondCut, address _init, bytes memory _calldata) internal {
for (uint256 facetIndex; facetIndex < _diamondCut.length;) {
LibDiamond.FacetCutAction action = _diamondCut[facetIndex].action;
if (action == LibDiamond.FacetCutAction.Add) {
addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else if (action == LibDiamond.FacetCutAction.Replace) {
replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else if (action == LibDiamond.FacetCutAction.Remove) {
removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else {
revert IncorrectFacetCutAction();
}
unchecked {
++facetIndex;
}
}
emit DiamondCut(_diamondCut, _init, _calldata);
initializeDiamondCut(_init, _calldata);
}
function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsZero();
}
uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
if (!LibUtil.isZeroAddress(oldFacetAddress)) {
revert FunctionAlreadyExists();
}
addFunction(ds, selector, selectorPosition, _facetAddress);
unchecked {
++selectorPosition;
++selectorIndex;
}
}
}
function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsZero();
}
uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
if (oldFacetAddress == _facetAddress) {
revert FunctionAlreadyExists();
}
removeFunction(ds, oldFacetAddress, selector);
addFunction(ds, selector, selectorPosition, _facetAddress);
unchecked {
++selectorPosition;
++selectorIndex;
}
}
}
function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
// if function does not exist then do nothing and return
if (!LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsNotZero();
}
for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
removeFunction(ds, oldFacetAddress, selector);
unchecked {
++selectorIndex;
}
}
}
function addFacet(DiamondStorage storage ds, address _facetAddress) internal {
enforceHasContractCode(_facetAddress);
ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length;
ds.facetAddresses.push(_facetAddress);
}
function addFunction(DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress)
internal
{
ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition;
ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector);
ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;
}
function removeFunction(DiamondStorage storage ds, address _facetAddress, bytes4 _selector) internal {
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FunctionDoesNotExist();
}
// an immutable function is a function defined directly in a diamond
if (_facetAddress == address(this)) {
revert FunctionIsImmutable();
}
// replace selector with last selector, then delete last selector
uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition;
uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1;
// if not the same then replace _selector with lastSelector
if (selectorPosition != lastSelectorPosition) {
bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition];
ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector;
ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition);
}
// delete the last selector
ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();
delete ds.selectorToFacetAndPosition[_selector];
// if no more selectors for facet address then delete the facet address
if (lastSelectorPosition == 0) {
// replace facet address with last facet address and delete last facet address
uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;
uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition;
if (facetAddressPosition != lastFacetAddressPosition) {
address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition];
ds.facetAddresses[facetAddressPosition] = lastFacetAddress;
ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition;
}
ds.facetAddresses.pop();
delete ds
.facetFunctionSelectors[_facetAddress]
.facetAddressPosition;
}
}
function initializeDiamondCut(address _init, bytes memory _calldata) internal {
if (LibUtil.isZeroAddress(_init)) {
if (_calldata.length != 0) {
revert InitZeroButCalldataNotEmpty();
}
} else {
if (_calldata.length == 0) {
revert CalldataEmptyButInitNotZero();
}
if (_init != address(this)) {
enforceHasContractCode(_init);
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory error) = _init.delegatecall(_calldata);
if (!success) {
if (error.length > 0) {
// bubble up the error
revert(string(error));
} else {
revert InitReverted();
}
}
}
}
function enforceHasContractCode(address _contract) internal view {
uint256 contractSize;
// solhint-disable-next-line no-inline-assembly
assembly {
contractSize := extcodesize(_contract)
}
if (contractSize == 0) {
revert FacetContainsNoCode();
}
}
}
"
},
"src/Interfaces/IExecutorTypes.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;
/// @title IExecutorTypes
/// @author ThyraWallet Team
/// @notice Type definitions for ExecutorFacet functionality with Merkle tree-based task execution
/// @custom:version 2.0.0
interface IExecutorTypes {
/// @notice Types of calls that can be made
enum CallType {
CALL,
DELEGATECALL
}
/// @notice Status of a task
enum TaskStatus {
INACTIVE, // Task is inactive (default state), operations cannot be executed
ACTIVE, // Task is active and operations can be executed
COMPLETED, // Task is completed, no more operations can be executed
CANCELLED // Task is cancelled, no more operations can be executed
}
/**
* @notice Data structure for a Merkle tree leaf node, representing a complete, verifiable operation
* @param target Target contract address for this operation
* @param value Amount of ETH to send with this call (msg.value)
* @param callData Complete encoded function call data
* @param callType Specifies whether this operation is CALL or DELEGATECALL
* @param operationId Unique ID within current task (Merkle tree) to prevent replay attacks for non-repeatable operations
* @param isRepeatable Flag indicating whether this operation can be executed multiple times
* @param startTime Start timestamp when this operation can be executed (Unix timestamp)
* @param endTime End timestamp when this operation can be executed (Unix timestamp)
* @param maxGasPrice Maximum gas price the executor is willing to pay (wei)
* @param gasLimit Maximum gas amount this operation can consume
* @param gasToken ERC20 token address used for gas payment (address(0) means native ETH)
*/
struct Operation {
// Core Execution Payload
address target;
uint256 value;
bytes callData;
CallType callType;
// Security & Validation Parameters
uint32 operationId;
bool isRepeatable;
uint32 startTime;
uint32 endTime;
uint256 maxGasPrice;
uint256 gasLimit;
address gasToken;
}
/**
* @notice EIP712 execution parameters structure for signing
* @param operation Operation type as uint8 (0=CALL, 1=DELEGATECALL)
* @param to Target contract address
* @param account Account address performing the operation
* @param executor Authorized executor address
* @param value Amount of ETH to send
* @param nonce Execution nonce for replay protection
* @param data Call data to execute
*/
struct ExecutionParams {
uint8 operation;
address to;
address account;
address executor;
uint256 value;
uint256 nonce;
bytes data;
}
}
"
},
"src/Interfaces/IAddressProviderService.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;
/// @title IAddressProviderService
/// @author Thyra.fi
/// @notice Interface for providing global service addresses to Diamond facets
/// @custom:version 1.0.0
interface IAddressProviderService {
/// @notice Get the ThyraRegistry contract address
/// @return The address of the ThyraRegistry contract
function getThyraRegistry() external view returns (address);
}
"
},
"src/Interfaces/IOwnershipFacet.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;
import {IERC173} from "./IERC173.sol";
/// @title Interface for OwnershipFacet (Extended)
/// @author ThyraWallet Team
/// @notice Extended ownership interface including Safe wallet initialization
/// @custom:version 1.0.0
interface IOwnershipFacet is IERC173 {
/// @notice Initialize Diamond with factory and Safe wallet
/// @param _factory Address of the Factory that deployed this Diamond
/// @param _safeWallet Address of the Safe wallet
function initialize(address _factory, address _safeWallet) external;
/// @notice Get the Safe wallet address
/// @return Safe wallet address (zero address if not initialized)
function safeWallet() external view returns (address);
/// @notice Get the factory address
/// @return Factory address that deployed this Diamond
function factory() external view returns (address);
}
"
},
"src/Libraries/bigMathMinified.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
/// @title library that represents a number in BigNumber(coefficient and exponent) format to store in smaller bits.
/// @notice the number is divided into two parts: a coefficient and an exponent. This comes at a cost of losing some precision
/// at the end of the number because the exponent simply fills it with zeroes. This precision is oftentimes negligible and can
/// result in significant gas cost reduction due to storage space reduction.
/// Also note, a valid big number is as follows: if the exponent is > 0, then coefficient last bits should be occupied to have max precision.
/// @dev roundUp is more like a increase 1, which happens everytime for the same number.
/// roundDown simply sets trailing digits after coefficientSize to zero (floor), only once for the same number.
library BigMathMinified {
/// @dev constants to use for `roundUp` input param to increase readability
bool internal constant ROUND_DOWN = false;
bool internal constant ROUND_UP = true;
/// @dev converts `normal` number to BigNumber with `exponent` and `coefficient` (or precision).
/// e.g.:
/// 5035703444687813576399599 (normal) = (coefficient[32bits], exponent[8bits])[40bits]
/// 5035703444687813576399599 (decimal) => 10000101010010110100000011111011110010100110100000000011100101001101001101011101111 (binary)
/// => 10000101010010110100000011111011000000000000000000000000000000000000000000000000000
/// ^-------------------- 51(exponent) -------------- ^
/// coefficient = 1000,0101,0100,1011,0100,0000,1111,1011 (2236301563)
/// exponent = 0011,0011 (51)
/// bigNumber = 1000,0101,0100,1011,0100,0000,1111,1011,0011,0011 (572493200179)
///
/// @param normal number which needs to be converted into Big Number
/// @param coefficientSize at max how many bits of precision there should be (64 = uint64 (64 bits precision))
/// @param exponentSize at max how many bits of exponent there should be (8 = uint8 (8 bits exponent))
/// @param roundUp signals if result should be rounded down or up
/// @return bigNumber converted bigNumber (coefficient << exponent)
function toBigNumber(uint256 normal, uint256 coefficientSize, uint256 exponentSize, bool roundUp)
internal
pure
returns (uint256 bigNumber)
{
assembly {
let lastBit_
let number_ := normal
if gt(number_, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
number_ := shr(0x80, number_)
lastBit_ := 0x80
}
if gt(number_, 0xFFFFFFFFFFFFFFFF) {
number_ := shr(0x40, number_)
lastBit_ := add(lastBit_, 0x40)
}
if gt(number_, 0xFFFFFFFF) {
number_ := shr(0x20, number_)
lastBit_ := add(lastBit_, 0x20)
}
if gt(number_, 0xFFFF) {
number_ := shr(0x10, number_)
lastBit_ := add(lastBit_, 0x10)
}
if gt(number_, 0xFF) {
number_ := shr(0x8, number_)
lastBit_ := add(lastBit_, 0x8)
}
if gt(number_, 0xF) {
number_ := shr(0x4, number_)
lastBit_ := add(lastBit_, 0x4)
}
if gt(number_, 0x3) {
number_ := shr(0x2, number_)
lastBit_ := add(lastBit_, 0x2)
}
if gt(number_, 0x1) { lastBit_ := add(lastBit_, 1) }
if gt(number_, 0) { lastBit_ := add(lastBit_, 1) }
if lt(lastBit_, coefficientSize) {
// for throw exception
lastBit_ := coefficientSize
}
let exponent := sub(lastBit_, coefficientSize)
let coefficient := shr(exponent, normal)
if and(roundUp, gt(exponent, 0)) {
// rounding up is only needed if exponent is > 0, as otherwise the coefficient fully holds the original number
coefficient := add(coefficient, 1)
if eq(shl(coefficientSize, 1), coefficient) {
// case were coefficient was e.g. 111, with adding 1 it became 1000 (in binary) and coefficientSize 3 bits
// final coefficient would exceed it's size. -> reduce coefficent to 100 and increase exponent by 1.
coefficient := shl(sub(coefficientSize, 1), 1)
exponent := add(exponent, 1)
}
}
if iszero(lt(exponent, shl(exponentSize, 1))) {
// if exponent is >= exponentSize, the normal number is too big to fit within
// BigNumber with too small sizes for coefficient and exponent
revert(0, 0)
}
bigNumber := shl(exponentSize, coefficient)
bigNumber := add(bigNumber, exponent)
}
}
/// @dev get `normal` number from `bigNumber`, `exponentSize` and `exponentMask`
function fromBigNumber(uint256 bigNumber, uint256 exponentSize, uint256 exponentMask)
internal
pure
returns (uint256 normal)
{
assembly {
let coefficient := shr(exponentSize, bigNumber)
let exponent := and(bigNumber, exponentMask)
normal := shl(exponent, coefficient)
}
}
/// @dev gets the most significant bit `lastBit` of a `normal` number (length of given number of binary format).
/// e.g.
/// 5035703444687813576399584 = 10000101010010110100000011111011110010100110100000000011100101001101001101011100000
/// lastBit = ^--------------------------------- 83 ----------------------------------------^
function mostSignificantBit(uint256 normal) internal pure returns (uint256 lastBit) {
assembly {
let number_ := normal
if gt(normal, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
number_ := shr(0x80, number_)
lastBit := 0x80
}
if gt(number_, 0xFFFFFFFFFFFFFFFF) {
number_ := shr(0x40, number_)
lastBit := add(lastBit, 0x40)
}
if gt(number_, 0xFFFFFFFF) {
number_ := shr(0x20, number_)
lastBit := add(lastBit, 0x20)
}
if gt(number_, 0xFFFF) {
number_ := shr(0x10, number_)
lastBit := add(lastBit, 0x10)
}
if gt(number_, 0xFF) {
number_ := shr(0x8, number_)\
Submitted on: 2025-10-03 11:33:22
Comments
Log in to comment.
No comments yet.