ExecutorFacet

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_)\

Tags:
Multisig, Upgradeable, Multi-Signature, Factory|addr:0x31cad720b2bbcbc799e0edee0ec2a61c40ab9667|verified:true|block:23496070|tx:0xd5e5449bfa7c3989c984362ae2ee7f373109d00603a6194168e7c85bc49aacf3|first_check:1759484000

Submitted on: 2025-10-03 11:33:22

Comments

Log in to comment.

No comments yet.