HIGH severity: Missing Safe Transfer Validation in Execution Logic. The `beforeHook` function in the `MultiTokenPeriodEnforcer` contract performs low-level calls to execute ERC-20 token transfers but does not validate that the transfer returned `true`, as required by the ERC-20 standard. This omission is due to the lack of a success check on the return value of the...

Vulnerable code:
// SPDX-License-Identifier: MIT AND Apache-2.0
pragma solidity 0.8.23;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";

import { CaveatEnforcer } from "./CaveatEnforcer.sol";
import { ModeCode } from "../utils/Types.sol";

/**
 * @title MultiTokenPeriodEnforcer
 * @notice Enforces periodic transfer limits for multiple tokens per delegation.
 * @dev The enforcer expects the _terms to be a concatenation of one or more 116-byte configurations.
 *      Each 116-byte segment encodes:
 *        - 20 bytes: token address (address(0) indicates a native transfer)
 *        - 32 bytes: periodAmount.
 *        - 32 bytes: periodDuration (in seconds).
 *        - 32 bytes: startDate for the first period.
 *
 *      For optimal gas usage, it is recommended that the configurations in _terms are sorted
 *      from the most frequently used token to the least frequently used token. Tokens placed
 *      earlier in the sequence will be processed first, reducing gas consumption.
 *
 *      The logic does not support duplicated token entries in the _terms. In the event that
 *      duplicate tokens are provided, the enforcer will only consider the configuration for the
 *      first occurrence and ignore subsequent configurations. This design choice is made to
 *      optimize gas efficiency and will not result in a revert.
 *
 *      Additionally, the enforcer does not support restrictions on the recipient address or
 *      arbitrary calldata. For ERC20 transfers, the execution data is strictly required to
 *      match the IERC20.transfer function selector with a zero ETH value, and for native transfers,
 *      only an empty calldata is permitted.
 *
 *      The _executionCallData always contains instructions for one token transfer.
 */
contract MultiTokenPeriodEnforcer is CaveatEnforcer {
    using ExecutionLib for bytes;

    ////////////////////////////// State //////////////////////////////

    struct PeriodicAllowance {
        uint256 periodAmount; // Maximum transferable amount per period.
        uint256 periodDuration; // Duration of each period in seconds.
        uint256 startDate; // Timestamp when the first period begins.
        uint256 lastTransferPeriod; // The period index in which the last transfer was made.
        uint256 transferredInCurrentPeriod; // Cumulative amount transferred in the current period.
    }

    // Mapping from delegation manager => delegation hash => token address => PeriodicAllowance.
    mapping(address delegationManager => mapping(bytes32 delegationHash => mapping(address token => PeriodicAllowance))) public
        periodicAllowances;

    ////////////////////////////// Events //////////////////////////////

    /**
     * @notice Emitted when a transfer is made and the allowance is updated.
     * @param sender The address initiating the transfer.
     * @param redeemer The address receiving the tokens/ETH.
     * @param delegationHash The hash identifying the delegation.
     * @param token The token contract address; for native transfers this is address(0).
     * @param periodAmount The maximum transferable amount per period.
     * @param periodDuration The duration of each period in seconds.
     * @param startDate The timestamp when the first period begins.
     * @param transferredInCurrentPeriod The total transferred in the current period after this transfer.
     * @param transferTimestamp The block timestamp when the transfer occurred.
     */
    event TransferredInPeriod(
        address indexed sender,
        address indexed redeemer,
        bytes32 indexed delegationHash,
        address token,
        uint256 periodAmount,
        uint256 periodDuration,
        uint256 startDate,
        uint256 transferredInCurrentPeriod,
        uint256 transferTimestamp
    );

    ////////////////////////////// Public Methods //////////////////////////////

    /**
     * @notice Retrieves the available amount along with period details for a specific token.
     * @param _delegationHash The delegation hash.
     * @param _delegationManager The delegation manager's address.
     * @param _terms A concatenation of one or more 116-byte configurations.
     * @param _token The token for which the available amount is requested (address(0) for native).
     * @return availableAmount_ The remaining transferable amount in the current period.
     * @return isNewPeriod_ True if a new period has begun.
     * @return currentPeriod_ The current period index.
     */
    function getAvailableAmount(
        bytes32 _delegationHash,
        address _delegationManager,
        bytes calldata _terms,
        address _token
    )
        external
        view
        returns (uint256 availableAmount_, bool isNewPeriod_, uint256 currentPeriod_)
    {
        PeriodicAllowance memory storedAllowance_ = periodicAllowances[_delegationManager][_delegationHash][_token];
        if (storedAllowance_.startDate != 0) {
            return _getAvailableAmount(storedAllowance_);
        }

        // Not yet initialized; simulate using provided terms.
        (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) = getTermsInfo(_terms, _token);

        PeriodicAllowance memory allowance_ = PeriodicAllowance({
            periodAmount: periodAmount_,
            periodDuration: periodDuration_,
            startDate: startDate_,
            lastTransferPeriod: 0,
            transferredInCurrentPeriod: 0
        });
        return _getAvailableAmount(allowance_);
    }

    /**
     * @notice Hook called before a transfer to enforce the periodic limit.
     * @dev For ERC20 transfers, expects _executionCallData to decode to (target,, callData)
     *      with callData length of 68, beginning with IERC20.transfer.selector and zero value.
     *      For native transfers, expects _executionCallData to decode to (target, value, callData)
     *      with an empty callData.
     * @param _terms A concatenation of one or more 116-byte configurations.
     * @param _mode The execution mode (must be single callType, default execType).
     * @param _executionCallData The encoded execution data.
     * @param _delegationHash The delegation hash.
     * @param _redeemer The address intended to receive the tokens/ETH.
     */
    function beforeHook(
        bytes calldata _terms,
        bytes calldata,
        ModeCode _mode,
        bytes calldata _executionCallData,
        bytes32 _delegationHash,
        address,
        address _redeemer
    )
        public
        override
        onlySingleCallTypeMode(_mode)
        onlyDefaultExecutionMode(_mode)
    {
        _validateAndConsumeTransfer(_terms, _executionCallData, _delegationHash, _redeemer);
    }

    /**
     * @notice Searches the provided _terms for a configuration matching _token.
     * @dev Expects _terms length to be a multiple of 116.
     * @param _terms A concatenation of 116-byte configurations.
     * @param _token The token address to search for (address(0) for native transfers).
     * @return periodAmount_ The maximum transferable amount for this token.
     * @return periodDuration_ The period duration (in seconds) for this token.
     * @return startDate_ The start date for the first period.
     */
    function getTermsInfo(
        bytes calldata _terms,
        address _token
    )
        public
        pure
        returns (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_)
    {
        uint256 termsLength_ = _terms.length;
        require(termsLength_ != 0 && termsLength_ % 116 == 0, "MultiTokenPeriodEnforcer:invalid-terms-length");

        // Iterate over the byte offset directly in increments of 116 bytes.
        for (uint256 offset_ = 0; offset_ < termsLength_;) {
            // Extract token address from the first 20 bytes.
            address token_ = address(bytes20(_terms[offset_:offset_ + 20]));
            if (token_ == _token) {
                // Get periodAmount from the next 32 bytes.
                periodAmount_ = uint256(bytes32(_terms[offset_ + 20:offset_ + 52]));
                // Get periodDuration from the subsequent 32 bytes.
                periodDuration_ = uint256(bytes32(_terms[offset_ + 52:offset_ + 84]));
                // Get startDate from the final 32 bytes.
                startDate_ = uint256(bytes32(_terms[offset_ + 84:offset_ + 116]));
                return (periodAmount_, periodDuration_, startDate_);
            }

            unchecked {
                offset_ += 116;
            }
        }
        revert("MultiTokenPeriodEnforcer:token-config-not-found");
    }

    /**
     * @notice Decodes all configurations contained in _terms.
     * @dev Expects _terms length to be a multiple of 116.
     * @param _terms A concatenation of 116-byte configurations.
     * @return tokens_ An array of token addresses.
     * @return periodAmounts_ An array of period amounts.
     * @return periodDurations_ An array of period durations (in seconds).
     * @return startDates_ An array of start dates for the first period.
     */
    function getAllTermsInfo(bytes calldata _terms)
        public
        pure
        returns (
            address[] memory tokens_,
            uint256[] memory periodAmounts_,
            uint256[] memory periodDurations_,
            uint256[] memory startDates_
        )
    {
        uint256 termsLength_ = _terms.length;
        require(termsLength_ % 116 == 0 && termsLength_ != 0, "MultiTokenPeriodEnforcer:invalid-terms-length");
        uint256 numConfigs_ = termsLength_ / 116;
        tokens_ = new address[](numConfigs_);
        periodAmounts_ = new uint256[](numConfigs_);
        periodDurations_ = new uint256[](numConfigs_);
        startDates_ = new uint256[](numConfigs_);

        // Loop over each configuration using its index.
        for (uint256 i = 0; i < numConfigs_; ++i) {
            // Calculate the starting offset for this configuration.
            uint256 offset_ = i * 116;
            // Get the token address from the first 20 bytes.
            tokens_[i] = address(bytes20(_terms[offset_:offset_ + 20]));
            // Get the periodAmount from the next 32 bytes.
            periodAmounts_[i] = uint256(bytes32(_terms[offset_ + 20:offset_ + 52]));
            // Get the periodDuration from the following 32 bytes.
            periodDurations_[i] = uint256(bytes32(_terms[offset_ + 52:offset_ + 84]));
            // Get the startDate from the final 32 bytes.
            startDates_[i] = uint256(bytes32(_terms[offset_ + 84:offset_ + 116]));
        }
    }

    ////////////////////////////// Internal Methods //////////////////////////////

    /**
     * @notice Validates and consumes a transfer (native or ERC20) by ensuring the amount does not exceed the available limit.
     * @dev Decodes the execution data based on token type:
     *      - For native transfers (_token == address(0)): expect no calldata, value greater than zero.
     *      - For ERC20 transfers (_token != address(0)): requires callData length to be 68 with a
     *        valid IERC20.transfer selector, and zero value.
     * @param _terms The concatenated configurations.
     * @param _executionCallData The encoded execution data.
     * @param _delegationHash The delegation hash.
     * @param _redeemer The address intended to receive the tokens/ETH.
     */
    function _validateAndConsumeTransfer(
        bytes calldata _terms,
        bytes calldata _executionCallData,
        bytes32 _delegationHash,
        address _redeemer
    )
        private
    {
        uint256 transferAmount_;
        address token_;

        // Decode _executionCallData using decodeSingle.
        (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle();

        if (callData_.length == 68) {
            // ERC20 transfer.
            require(value_ == 0, "MultiTokenPeriodEnforcer:invalid-value-in-erc20-transfer");
            require(bytes4(callData_[0:4]) == IERC20.transfer.selector, "MultiTokenPeriodEnforcer:invalid-method");
            token_ = target_;
            transferAmount_ = uint256(bytes32(callData_[36:68]));
        } else if (callData_.length == 0) {
            // Native transfer.
            require(value_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-value-in-native-transfer");
            token_ = address(0);
            transferAmount_ = value_;
        } else {
            // If callData length is neither 68 nor 0, revert.
            revert("MultiTokenPeriodEnforcer:invalid-call-data-length");
        }

        // Retrieve the configuration for the token from _terms.
        (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) = getTermsInfo(_terms, token_);

        // Use the multi-token mapping.
        PeriodicAllowance storage allowance_ = periodicAllowances[msg.sender][_delegationHash][token_];

        // Initialize the allowance if not already set.
        if (allowance_.startDate == 0) {
            require(startDate_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-start-date");
            require(periodAmount_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-period-amount");
            require(periodDuration_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-period-duration");
            require(block.timestamp >= startDate_, "MultiTokenPeriodEnforcer:transfer-not-started");

            allowance_.periodAmount = periodAmount_;
            allowance_.periodDuration = periodDuration_;
            allowance_.startDate = startDate_;
        }

        // Determine the available amount.
        (uint256 availableAmount_, bool isNewPeriod_, uint256 currentPeriod_) = _getAvailableAmount(allowance_);
        require(transferAmount_ <= availableAmount_, "MultiTokenPeriodEnforcer:transfer-amount-exceeded");

        // Reset transferred amount if a new period has begun.
        if (isNewPeriod_) {
            allowance_.lastTransferPeriod = currentPeriod_;
            allowance_.transferredInCurrentPeriod = 0;
        }
        allowance_.transferredInCurrentPeriod += transferAmount_;

        emit TransferredInPeriod(
            msg.sender,
            _redeemer,
            _delegationHash,
            token_,
            periodAmount_,
            periodDuration_,
            startDate_,
            allowance_.transferredInCurrentPeriod,
            block.timestamp
        );
    }

    /**
     * @notice Computes the available amount for the current period.
     * @dev If block.timestamp is before startDate, available amount is 0.
     * @param _allowance The PeriodicAllowance struct.
     * @return availableAmount_ The remaining transferable amount in the current period.
     * @return isNewPeriod_ True if the last transfer period is not equal to the current period.
     * @return currentPeriod_ The current period index.
     */
    function _getAvailableAmount(PeriodicAllowance memory _allowance)
        internal
        view
        returns (uint256 availableAmount_, bool isNewPeriod_, uint256 currentPeriod_)
    {
        if (block.timestamp < _allowance.startDate) {
            return (0, false, 0);
        }

        currentPeriod_ = (block.timestamp - _allowance.startDate) / _allowance.periodDuration + 1;
        isNewPeriod_ = (_allowance.lastTransferPeriod != currentPeriod_);
        uint256 alreadyTransferred_ = isNewPeriod_ ? 0 : _allowance.transferredInCurrentPeriod;
        availableAmount_ = _allowance.periodAmount > alreadyTransferred_ ? _allowance.periodAmount - alreadyTransferred_ : 0;
    }
}

// SPDX-License-Identifier: MIT AND Apache-2.0
pragma solidity 0.8.23;

import { IEntryPoint } from "@account-abstraction/interfaces/IEntryPoint.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { ERC1967Utils } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import { ModeLib } from "@erc7579/lib/ModeLib.sol";
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
import { ExecutionHelper } from "@erc7579/core/ExecutionHelper.sol";
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

import { ERC1271Lib } from "./libraries/ERC1271Lib.sol";
import { IDeleGatorCore } from "./interfaces/IDeleGatorCore.sol";
import { IDelegationManager } from "./interfaces/IDelegationManager.sol";
import { CallType, ExecType, Execution, Delegation, PackedUserOperation, ModeCode } from "./utils/Types.sol";
import { CALLTYPE_SINGLE, CALLTYPE_BATCH, EXECTYPE_DEFAULT, EXECTYPE_TRY } from "./utils/Constants.sol";

/**
 * @title DeleGatorCore
 * @notice This contract contains the shared logic for a DeleGator SCA implementation.
 * @dev Implements the interface needed for a DelegationManager to interact with a DeleGator implementation.
 * @dev DeleGator implementations can inherit this to enable Delegation, ERC4337 and UUPS.
 * @dev DeleGator implementations MUST use Namespaced Storage to ensure subsequent UUPS implementation updates are safe.
 */
abstract contract DeleGatorCore is
    Initializable,
    ExecutionHelper,
    UUPSUpgradeable,
    IERC165,
    IDeleGatorCore,
    IERC721Receiver,
    IERC1155Receiver,
    EIP712
{
    using ModeLib for ModeCode;
    using ExecutionLib for bytes;

    ////////////////////////////// State //////////////////////////////

    /// @dev The DelegationManager contract that has root access to this contract
    IDelegationManager public immutable delegationManager;

    /// @dev The EntryPoint contract that has root access to this contract
    IEntryPoint public immutable entryPoint;

    /// @dev The typehash for the PackedUserOperation struct
    bytes32 public constant PACKED_USER_OP_TYPEHASH = keccak256(
        "PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData,address entryPoint)"
    );

    ////////////////////////////// Events //////////////////////////////

    /// @dev Emitted when the Delegation manager is set
    event SetDelegationManager(IDelegationManager indexed newDelegationManager);

    /// @dev Emitted when the EntryPoint is set
    event SetEntryPoint(IEntryPoint indexed entryPoint);

    /// @dev Emitted when the storage is cleared
    event ClearedStorage();

    /// @dev Event emitted when prefunding is sent.
    event SentPrefund(address indexed sender, uint256 amount, bool success);

    ////////////////////////////// Errors //////////////////////////////

    /// @dev Error thrown when the caller is not this contract.
    error NotSelf();

    /// @dev Error thrown when the caller is not the entry point.
    error NotEntryPoint();

    /// @dev Error thrown when the caller is not the EntryPoint or this contract.
    error NotEntryPointOrSelf();

    /// @dev Error thrown when the caller is not the delegation manager.
    error NotDelegationManager();

    /// @dev Error thrown when an execution with an unsupported CallType was made
    error UnsupportedCallType(CallType callType);

    /// @dev Error thrown when an execution with an unsupported ExecType was made
    error UnsupportedExecType(ExecType execType);

    ////////////////////////////// Modifiers //////////////////////////////

    /**
     * @notice Require the function call to come from the EntryPoint.
     * @dev Check that the caller is the entry point
     */
    modifier onlyEntryPoint() {
        if (msg.sender != address(entryPoint)) revert NotEntryPoint();
        _;
    }

    /**
     * @notice Require the function call to come from the EntryPoint or this contract.
     * @dev Check that the caller is either the delegator contract itself or the entry point
     */
    modifier onlyEntryPointOrSelf() {
        if (msg.sender != address(entryPoint) && msg.sender != address(this)) revert NotEntryPointOrSelf();
        _;
    }

    /**
     * @notice Require the function call to come from the DelegationManager.
     * @dev Check that the caller is the stored delegation manager.
     */
    modifier onlyDelegationManager() {
        if (msg.sender != address(delegationManager)) revert NotDelegationManager();
        _;
    }

    ////////////////////////////// Constructor //////////////////////////////

    /**
     * @notice Initializes the DeleGatorCore contract
     * @custom:oz-upgrades-unsafe-allow constructor
     * @param _delegationManager the address of the trusted DelegationManager contract that will have root access to this contract
     * @param _entryPoint the address of entry point
     * @param _name Name of the contract
     * @param _domainVersion Domain version of the contract
     */
    constructor(
        IDelegationManager _delegationManager,
        IEntryPoint _entryPoint,
        string memory _name,
        string memory _domainVersion
    )
        EIP712(_name, _domainVersion)
    {
        _disableInitializers();
        delegationManager = _delegationManager;
        entryPoint = _entryPoint;
        emit SetDelegationManager(_delegationManager);
        emit SetEntryPoint(_entryPoint);
    }

    ////////////////////////////// External Methods //////////////////////////////

    /**
     * @notice Allows this contract to receive the chains native token
     */
    receive() external payable { }

    /**
     * @notice Redeems a delegation on the DelegationManager and executes the specified executions on behalf of the root delegator.
     * @param _permissionContexts An array of bytes where each element is made up of an array
     * of `Delegation` structs that are used to validate the authority given to execute the corresponding execution on the
     * root delegator, ordered from leaf to root.
     * @param _modes An array of `ModeCode` structs representing the mode of execiton for each execution callData.
     * @param _executionCallDatas An array of `Execution` structs representing the executions to be executed.
     */
    function redeemDelegations(
        bytes[] calldata _permissionContexts,
        ModeCode[] calldata _modes,
        bytes[] calldata _executionCallDatas
    )
        external
        onlyEntryPointOrSelf
    {
        delegationManager.redeemDelegations(_permissionContexts, _modes, _executionCallDatas);
    }

    /**
     * @notice Executes an Execution from this contract
     * @dev This method is intended to be called through a UserOp which ensures the invoker has sufficient permissions
     * @dev This convenience method defeaults to reverting on failure and a single execution.
     * @param _execution The Execution to be executed
     */
    function execute(Execution calldata _execution) external payable onlyEntryPoint {
        _execute(_execution.target, _execution.value, _execution.callData);
    }

    /**
     * @notice Executes an Execution from this contract
     * @dev Related: @erc7579/MSAAdvanced.sol
     * @dev This method is intended to be called through a UserOp which ensures the invoker has sufficient permissions
     * @param _mode The ModeCode for the execution
     * @param _executionCalldata The calldata for the execution
     */
    function execute(ModeCode _mode, bytes calldata _executionCalldata) external payable onlyEntryPoint {
        (CallType callType_, ExecType execType_,,) = _mode.decode();

        // Check if calltype is batch or single
        if (callType_ == CALLTYPE_BATCH) {
            // destructure executionCallData according to batched exec
            Execution[] calldata executions_ = _executionCalldata.decodeBatch();
            // Check if execType is revert or try
            if (execType_ == EXECTYPE_DEFAULT) _execute(executions_);
            else if (execType_ == EXECTYPE_TRY) _tryExecute(executions_);
            else revert UnsupportedExecType(execType_);
        } else if (callType_ == CALLTYPE_SINGLE) {
            // Destructure executionCallData according to single exec
            (address target_, uint256 value_, bytes calldata callData_) = _executionCalldata.decodeSingle();
            // Check if execType is revert or try
            if (execType_ == EXECTYPE_DEFAULT) {
                _execute(target_, value_, callData_);
            } else if (execType_ == EXECTYPE_TRY) {
                bytes[] memory returnData_ = new bytes[](1);
                bool success_;
                (success_, returnData_[0]) = _tryExecute(target_, value_, callData_);
                if (!success_) emit TryExecuteUnsuccessful(0, returnData_[0]);
            } else {
                revert UnsupportedExecType(execType_);
            }
        } else {
            revert UnsupportedCallType(callType_);
        }
    }

    /**
     * @inheritdoc IDeleGatorCore
     * @dev Related: @erc7579/MSAAdvanced.sol
     */
    function executeFromExecutor(
        ModeCode _mode,
        bytes calldata _executionCalldata
    )
        external
        payable
        onlyDelegationManager
        returns (bytes[] memory returnData_)
    {
        (CallType callType_, ExecType execType_,,) = _mode.decode();

        // Check if calltype is batch or single
        if (callType_ == CALLTYPE_BATCH) {
            // Destructure executionCallData according to batched exec
            Execution[] calldata executions_ = _executionCalldata.decodeBatch();
            // check if execType is revert or try
            if (execType_ == EXECTYPE_DEFAULT) returnData_ = _execute(executions_);
            else if (execType_ == EXECTYPE_TRY) returnData_ = _tryExecute(executions_);
            else revert UnsupportedExecType(execType_);
        } else if (callType_ == CALLTYPE_SINGLE) {
            // Destructure executionCallData according to single exec
            (address target_, uint256 value_, bytes calldata callData_) = _executionCalldata.decodeSingle();
            returnData_ = new bytes[](1);
            bool success_;
            // check if execType is revert or try
            if (execType_ == EXECTYPE_DEFAULT) {
                returnData_[0] = _execute(target_, value_, callData_);
            } else if (execType_ == EXECTYPE_TRY) {
                (success_, returnData_[0]) = _tryExecute(target_, value_, callData_);
                if (!success_) emit TryExecuteUnsuccessful(0, returnData_[0]);
            } else {
                revert UnsupportedExecType(execType_);
            }
        } else {
            revert UnsupportedCallType(callType_);
        }
    }

    /**
     * @notice Validates a UserOp signature and sends any necessary funds to the EntryPoint
     * @dev Related: ERC4337
     * @param _userOp The UserOp struct to validate
     * @param _missingAccountFunds The missing funds from the account
     * @return validationData_ The validation data
     */
    function validateUserOp(
        PackedUserOperation calldata _userOp,
        bytes32, // Ignore UserOpHash from the Entry Point
        uint256 _missingAccountFunds
    )
        external
        onlyEntryPoint
        onlyProxy
        returns (uint256 validationData_)
    {
        validationData_ = _validateUserOpSignature(_userOp, getPackedUserOperationTypedDataHash(_userOp));
        _payPrefund(_missingAccountFunds);
    }

    /**
     * @inheritdoc IERC1271
     * @notice Verifies the signatures of the signers.
     * @dev Related: ERC4337, Delegation
     * @param _hash The hash of the data signed.
     * @param _signature The signatures of the signers.
     * @return magicValue_ A bytes4 magic value which is EIP1271_MAGIC_VALUE(0x1626ba7e) if the signature is valid, returns
     * SIG_VALIDATION_FAILED(0xffffffff) if there is a signature mismatch and reverts (for all other errors).
     */
    function isValidSignature(
        bytes32 _hash,
        bytes calldata _signature
    )
        external
        view
        override
        onlyProxy
        returns (bytes4 magicValue_)
    {
        return _isValidSignature(_hash, _signature);
    }

    /// @inheritdoc IERC721Receiver
    function onERC721Received(address, address, uint256, bytes memory) external view override onlyProxy returns (bytes4) {
        return this.onERC721Received.selector;
    }

    /// @inheritdoc IERC1155Receiver
    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes memory
    )
        external
        view
        override
        onlyProxy
        returns (bytes4)
    {
        return this.onERC1155Received.selector;
    }

    /// @inheritdoc IERC1155Receiver
    function onERC1155BatchReceived(
        address,
        address,
        uint256[] memory,
        uint256[] memory,
        bytes memory
    )
        public
        view
        override
        onlyProxy
        returns (bytes4)
    {
        return this.onERC1155BatchReceived.selector;
    }

    /**
     * @notice Deposits more funds for this account in the entry point
     * @dev Related: ERC4337
     */
    function addDeposit() external payable onlyProxy {
        entryPoint.depositTo{ value: msg.value }(address(this));
    }

    /**
     * @notice This method withdraws funds of this account from the entry point
     * @dev Related: ERC4337
     * @param _withdrawAddress Address to withdraw the amount to
     * @param _withdrawAmount Amount to be withdraw from the entry point
     */
    function withdrawDeposit(address payable _withdrawAddress, uint256 _withdrawAmount) external onlyEntryPointOrSelf {
        entryPoint.withdrawTo(_withdrawAddress, _withdrawAmount);
    }

    /**
     * @notice Disables a delegation from being used
     * @param _delegation The delegation to be disabled
     */
    function disableDelegation(Delegation calldata _delegation) external onlyEntryPointOrSelf {
        delegationManager.disableDelegation(_delegation);
    }

    /**
     * @notice Enables a delegation to be used
     * @dev Delegations only need to be enabled if they have been disabled
     * @param _delegation The delegation to be enabled
     */
    function enableDelegation(Delegation calldata _delegation) external onlyEntryPointOrSelf {
        delegationManager.enableDelegation(_delegation);
    }

    /**
     * @notice Retains storage and updates the logic contract in use.
     * @dev Related: UUPS
     * @param _newImplementation Address of the new logic contract to use.
     * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
     */
    function upgradeToAndCallAndRetainStorage(address _newImplementation, bytes memory _data) external payable {
        super.upgradeToAndCall(_newImplementation, _data);
    }

    /**
     * @notice Clears storage by default and updates the logic contract in use.
     * @dev Related: UUPS
     * @param _newImplementation Address of the new logic contract to use.
     * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
     */
    function upgradeToAndCall(address _newImplementation, bytes memory _data) public payable override {
        _clearDeleGatorStorage();
        super.upgradeToAndCall(_newImplementation, _data);
    }

    /**
     * @notice Checks if the delegation is disabled
     * @param _delegationHash the hash of the delegation to check for disabled status.
     * @return bool is the delegation disabled
     */
    function isDelegationDisabled(bytes32 _delegationHash) external view returns (bool) {
        return delegationManager.disabledDelegations(_delegationHash);
    }

    /**
     * @notice Gets the current account's deposit in the entry point
     * @dev Related: ERC4337
     * @return uint256 The current account's deposit in the entry point
     */
    function getDeposit() external view returns (uint256) {
        return entryPoint.balanceOf(address(this));
    }

    /**
     * @notice Retrieves the address of the current UUPS Logic contract
     * @dev Related: UUPS
     * @return The address of the current UUPS Logic contract.
     */
    function getImplementation() external view returns (address) {
        return ERC1967Utils.getImplementation();
    }

    /**
     * @notice Retrieves the version of the current UUPS Logic contract
     * @dev This version number is moreso the count of initializations that have occurred for this proxy contract.
     * @dev Related: UUPS
     */
    function getInitializedVersion() external view returns (uint64) {
        return _getInitializedVersion();
    }

    /**
     * @notice Returns the next sequential nonce using a static key
     * @dev Related: ERC4337
     * @return uint256 The next sequential nonce
     */
    function getNonce() external view returns (uint256) {
        return entryPoint.getNonce(address(this), 0);
    }

    /**
     * @notice Returns the next sequential nonce using a custom key
     * @dev Related: ERC4337
     * @return uint256 The next sequential nonce for the key provided
     * @param _key The key to use for the nonce
     */
    function getNonce(uint192 _key) external view returns (uint256) {
        return entryPoint.getNonce(address(this), _key);
    }

    /**
     * @notice This method returns the domain hash used for signing typed data
     * @return bytes32 The domain hash
     */
    function getDomainHash() external view returns (bytes32) {
        return _domainSeparatorV4();
    }

    /**
     * @notice Returns the formatted hash to sign for an EIP712 typed data signature
     * @param _userOp the UserOp to hash
     * @notice Returns an EIP712 typed data hash for a given UserOp
     */
    function getPackedUserOperationTypedDataHash(PackedUserOperation calldata _userOp) public view returns (bytes32) {
        return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), getPackedUserOperationHash(_userOp));
    }

    /**
     * @inheritdoc IERC165
     * @dev Supports the following interfaces: IDeleGatorCore, IERC721Receiver, IERC1155Receiver, IERC165, IERC1271
     */
    function supportsInterface(bytes4 _interfaceId) public view virtual override(IERC165) onlyProxy returns (bool) {
        return _interfaceId == type(IDeleGatorCore).interfaceId || _interfaceId == type(IERC721Receiver).interfaceId
            || _interfaceId == type(IERC1155Receiver).interfaceId || _interfaceId == type(IERC165).interfaceId
            || _interfaceId == type(IERC1271).interfaceId;
    }

    /**
     * Provides the typed data hash for a PackedUserOperation
     * @param _userOp the PackedUserOperation to hash
     */
    function getPackedUserOperationHash(PackedUserOperation calldata _userOp) public view returns (bytes32) {
        return keccak256(
            abi.encode(
                PACKED_USER_OP_TYPEHASH,
                _userOp.sender,
                _userOp.nonce,
                keccak256(_userOp.initCode),
                keccak256(_userOp.callData),
                _userOp.accountGasLimits,
                _userOp.preVerificationGas,
                _userOp.gasFees,
                keccak256(_userOp.paymasterAndData),
                entryPoint
            )
        );
    }

    ////////////////////////////// Internal Methods //////////////////////////////

    /**
     * @notice The logic to verify if the signature is valid for this contract
     * @dev This is an internal function that should be overridden by the implementing contract based on the signature scheme used.
     * @dev Related: ERC4337
     * @param _hash The hash of the data signed.
     * @param _signature The signatures of the signers.
     * @return A bytes4 magic value which is EIP1271_MAGIC_VALUE(0x1626ba7e) if the signature is valid, returns
     * SIG_VALIDATION_FAILED(0xffffffff) if there is a signature mismatch and reverts (for all other errors).
     */
    function _isValidSignature(bytes32 _hash, bytes calldata _signature) internal view virtual returns (bytes4);

    /**
     * @notice Clears the storage being used by a DeleGator Implementation
     * @dev Related: UUPS
     */
    function _clearDeleGatorStorage() internal virtual;

    /**
     * @notice Validates that the sender is allowed to upgrade the contract
     * @dev Related: UUPS
     * @dev This is needed for UUPS secure upgradeability
     * @param _newImplementation The address of the new implementation.
     */
    function _authorizeUpgrade(address _newImplementation) internal override onlyEntryPointOrSelf { }

    /**
     * @notice Validates a UserOp signature and returns a code indicating if the signature is valid or not
     * @dev This method calls the DeleGator implementations `_isValidSignature` to validate the signature according to the
     * implementations auth scheme.
     * @dev Returns 0 if the signature is valid, 1 if the signature is invalid.
     * @dev Related: ERC4337
     * @param _userOp The UserOp
     * @param _userOpHash UserOp hash produced with typed data
     * @return validationData_ A code indicating if the signature is valid or not
     */
    function _validateUserOpSignature(
        PackedUserOperation calldata _userOp,
        bytes32 _userOpHash
    )
        internal
        view
        returns (uint256 validationData_)
    {
        bytes4 result_ = _isValidSignature(_userOpHash, _userOp.signature);
        if (result_ == ERC1271Lib.EIP1271_MAGIC_VALUE) {
            return 0;
        } else {
            return 1;
        }
    }

    /**
     * @notice Sends the entrypoint (msg.sender) any needed funds for the transaction.
     * @param _missingAccountFunds the minimum value this method should send the entrypoint.
     *         this value MAY be zero, in case there is enough deposit, or the userOp has a paymaster.
     */
    function _payPrefund(uint256 _missingAccountFunds) internal {
        if (_missingAccountFunds != 0) {
            (bool success_,) = payable(msg.sender).call{ value: _missingAccountFunds, gas: type(uint256).max }("");
            (success_);
            // Ignore failure (it's EntryPoint's job to verify, not account.)
            emit SentPrefund(msg.sender, _missingAccountFunds, success_);
        }
    }
}


Vulnerability type: CWE-703|CWE-754|CWE-252