RescueModule

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/modules/base/RescueModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Contracts
import { BaseModule } from "@modules/base/BaseModule.sol";

// Interfaces
import { IRescueModule } from "@modules/interfaces/IRescueModule.sol";
import { IModule } from "@modules/interfaces/IModule.sol";

// Libraries
import { ModuleManagerLib } from "@modules/libraries/ModuleManagerLib.sol";
import { ERC20UtilsLib } from "@modules/libraries/ERC20UtilsLib.sol";

/// @title Rescue Module
/// @notice A module which allows an owner to rescue stuck funds
contract RescueModule is BaseModule, IRescueModule {
    /*//////////////////////////////////////////////////////////////
                               LIBRARIES
    //////////////////////////////////////////////////////////////*/

    using ERC20UtilsLib for address;

    /*//////////////////////////////////////////////////////////////
                               CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Native ETH address
    address internal constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    /*//////////////////////////////////////////////////////////////
                              CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _portikusV2) BaseModule("Rescue Module", "1.0.0", _portikusV2) { }

    /*//////////////////////////////////////////////////////////////
                               MODIFIERS
    //////////////////////////////////////////////////////////////*/

    /// @notice Verifies that the caller is the owner of the adapter,
    ///         reverts if the caller is not the owner with UnauthorizedAccount(msg.sender)
    modifier onlyOwner() {
        ModuleManagerLib.isOwner();
        _;
    }

    /*//////////////////////////////////////////////////////////////
                                  RESCUE
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IRescueModule
    function rescue(TransferData[] calldata transfers) external onlyOwner {
        for (uint256 i = 0; i < transfers.length; i++) {
            transfers[i].token.transferTo(transfers[i].recipient, transfers[i].amount);
        }
    }

    /*//////////////////////////////////////////////////////////////
                               SELECTORS
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IModule
    function selectors() external pure override(BaseModule, IModule) returns (bytes4[] memory moduleSelectors) {
        moduleSelectors = new bytes4[](1);
        moduleSelectors[0] = this.rescue.selector;
    }
}
"
    },
    "src/modules/base/BaseModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Contracts
import { ReentrancyGuardTransient as ReentrancyGuard } from "@openzeppelin/utils/ReentrancyGuardTransient.sol";

// Interfaces
import { IModule } from "@modules/interfaces/IModule.sol";

// Libraries
import { ShortStrings, ShortString } from "@openzeppelin/utils/ShortStrings.sol";

/// @title Base Module
/// @notice An abstract base module to be inherited by all modules in the Portikus V2 protocol
abstract contract BaseModule is ReentrancyGuard, IModule {
    /*//////////////////////////////////////////////////////////////
                               LIBRARIES
    //////////////////////////////////////////////////////////////*/

    using ShortStrings for string;
    using ShortStrings for ShortString;

    /*//////////////////////////////////////////////////////////////
                               CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @notice The name and version of the module
    ShortString private immutable NAME;
    ShortString private immutable VERSION;
    /// @notice The address of the Portikus V2 contract
    address public immutable PORTIKUS_V2;

    /*//////////////////////////////////////////////////////////////
                                CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    /// @param _name The name of the module
    /// @param _version The version of the module
    constructor(string memory _name, string memory _version, address _portikusV2) {
        NAME = _name.toShortString();
        VERSION = _version.toShortString();
        PORTIKUS_V2 = _portikusV2;
    }

    /*//////////////////////////////////////////////////////////////
                                METADATA
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IModule
    function name() external view virtual override returns (string memory) {
        return NAME.toString();
    }

    /// @inheritdoc IModule
    function version() external view virtual override returns (string memory) {
        return VERSION.toString();
    }

    /*//////////////////////////////////////////////////////////////
                                SELECTORS
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IModule
    function selectors() external pure virtual override returns (bytes4[] memory moduleSelectors);
}
"
    },
    "src/modules/interfaces/IRescueModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { IModule } from "@modules/interfaces/IModule.sol";

/// @title Rescue Module Interface
/// @notice Interface for the Rescue Module, which allows an owner to rescue stuck funds
interface IRescueModule is IModule {
    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    //////////////////////////////////////////////////////////////*/

    /// @dev Data for token rescue
    struct TransferData {
        /// @dev The token to rescue
        address token;
        /// @dev The amount to rescue
        uint256 amount;
        // @dev The token amount recipient
        address recipient;
    }

    /*//////////////////////////////////////////////////////////////
                                  RESCUE
    //////////////////////////////////////////////////////////////*/

    /// @notice Allows an owner to rescue stuck funds
    /// @param transfers Data to perform rescue transfers
    function rescue(TransferData[] calldata transfers) external;
}
"
    },
    "src/modules/interfaces/IModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/// @title IModule
/// @notice Core interfaces that all modules must implement to be compatible with the Portikus protocol
interface IModule {
    /*//////////////////////////////////////////////////////////////
                                METADATA
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns the name of the module
    function name() external view returns (string memory);

    /// @notice Returns the version of the module
    function version() external view returns (string memory);

    /*//////////////////////////////////////////////////////////////
                               SELECTORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Used by the executor to determine which functions should be installed
    /// @dev The implementation should not include any of the function selectors defined in the IModule interface itself
    /// @return moduleSelectors An array of function selectors that the module implements
    function selectors() external pure returns (bytes4[] memory moduleSelectors);
}
"
    },
    "src/modules/libraries/ModuleManagerLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Interfaces
import { IModule } from "@modules/interfaces/IModule.sol";
import { IAdapter } from "@adapter/interfaces/IAdapter.sol";

/// @title Module Manager Library
/// @notice A library for managing modules in an adapter contract, heavily inspired by the Diamond Proxy (ERC-2535)
/// @author Laita Labs
library ModuleManagerLib {
    /*//////////////////////////////////////////////////////////////
                                 ERRORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Emitted when a selector from a module is already set
    error SelectorAlreadySet(bytes4 selector, address oldModule);

    /// @notice Emitted when trying to uninstall a module that is not installed
    error ModuleNotInstalled(address module);

    /// @notice Emitted when caller is not the owner
    error UnauthorizedAccount(address account);

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Emitted when a module is installed
    event ModuleInstalled(address indexed module);

    /// @notice Emitted when a module is uninstalled
    event ModuleUninstalled(address indexed module);

    /// @notice Emitted when the adapter ownership is transferred
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /*//////////////////////////////////////////////////////////////
                                STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice keccak256(abi.encode(uint256(keccak256("ModuleManagerLib.modules")) - 1)) & ~bytes32(uint256(0xff));
    bytes32 internal constant MODULES_SLOT = 0xcd83f9e468adb540d21d5a132f84948f7344c9d0a65c67a05f93f89a07b57200;

    /// @dev A struct to store a module's address data
    /// @param moduleAddress The address of the module
    /// @param functionSelectorPosition The position of the module's function selectors in the
    ///        ModuleToSelectors.selectors array
    struct ModuleToFacet {
        address moduleAddress;
        uint32 functionSelectorPosition;
    }

    /// @dev A struct to store a module's function selectors data
    /// @param selectors The function selectors of the module
    /// @param moduleAddressPosition The position of the module in the ModuleStorage.modules array
    struct ModuleToSelectors {
        bytes4[] selectors;
        uint32 moduleAddressPosition;
    }

    /// @custom:storage-location erc7201:ModuleManagerLib.modules
    /// @notice The structure that defines the storage layout containing all module data, storage collisions are avoided
    ///         following the ERC-7201 standard
    /// @param moduleToSelectors A mapping of module addresses to their function selectors
    /// @param selectorToModule A mapping of function selectors to their module addresses
    /// @param modules An array of all module addresses
    /// @param owner The adapter owner
    struct ModuleStorage {
        mapping(address => ModuleToSelectors) moduleToSelectors;
        mapping(bytes4 => ModuleToFacet) selectorToModule;
        address[] modules;
        address owner;
    }

    /// @notice Get the storage slot for the ModuleStorage struct
    /// @return ms The ModuleStorage struct storage pointer
    function modulesStorage() internal pure returns (ModuleStorage storage ms) {
        bytes32 storagePointer = MODULES_SLOT;
        assembly {
            ms.slot := storagePointer
        }
    }

    /*//////////////////////////////////////////////////////////////
                                INSTALL
    //////////////////////////////////////////////////////////////*/

    /// @notice Install a module in the adapter, adding all of its function selectors
    /// @param module The address of the module to install
    function install(address module) internal {
        // Get adapter module storage
        ModuleStorage storage ms = modulesStorage();
        // Get module function selectors
        bytes4[] memory selectors = IModule(module).selectors();

        // Add module to modules
        ms.modules.push(module);

        // Set selectors in moduleToSelectors
        ms.moduleToSelectors[module].selectors = selectors;

        // Set module address position in moduleToSelectors
        ms.moduleToSelectors[module].moduleAddressPosition = uint32(ms.modules.length - 1);

        // Set module in selectorToModule
        for (uint32 i = 0; i < selectors.length; i++) {
            address oldModule = ms.selectorToModule[selectors[i]].moduleAddress;
            // If a selector is already set, revert as it would cause a conflict
            if (oldModule != address(0)) {
                // If a selector is already set the owner should uninstall the old module first
                revert SelectorAlreadySet(selectors[i], oldModule);
            }
            ms.selectorToModule[selectors[i]].functionSelectorPosition = i;
            ms.selectorToModule[selectors[i]].moduleAddress = module;
        }
        // emit the module installed event
        emit ModuleInstalled(module);
    }

    /*//////////////////////////////////////////////////////////////
                               UNINSTALL
    //////////////////////////////////////////////////////////////*/

    /// @notice Remove a module from the adapter, removing all of its function selectors
    /// @param module The address of the module to uninstall
    function uninstall(address module) internal {
        // Get adapter module storage
        ModuleStorage storage ms = modulesStorage();
        // Get module function selectors
        bytes4[] memory selectors = ms.moduleToSelectors[module].selectors;

        // Check if the module is actually installed
        if (selectors.length == 0) {
            revert ModuleNotInstalled(module);
        }

        // Get the module position in modules
        uint32 modulePosition = ms.moduleToSelectors[module].moduleAddressPosition;
        // Get the last module position in modules
        uint32 lastModulePosition = uint32(ms.modules.length - 1);
        // If the module is not the last module, swap the module with the last module
        if (modulePosition != lastModulePosition) {
            address lastModule = ms.modules[lastModulePosition];
            ms.modules[modulePosition] = lastModule;
            ms.moduleToSelectors[lastModule].moduleAddressPosition = modulePosition;
        }
        // Remove the last module
        ms.modules.pop();

        // Remove module from moduleToSelectors
        delete ms.moduleToSelectors[module];

        // Remove module from selectorToModule
        for (uint256 i = 0; i < selectors.length; i++) {
            delete ms.selectorToModule[selectors[i]];
        }
        // emit the module uninstalled event
        emit ModuleUninstalled(module);
    }

    /*//////////////////////////////////////////////////////////////
                                GETTERS
    //////////////////////////////////////////////////////////////*/

    /// @notice Get all installed modules and their selectors
    /// @return modules The installed modules and their selectors
    // TODO: update to internal view to prevent making ModuleManagerLib as external library
    function getModules() external view returns (IAdapter.Module[] memory modules) {
        // Get adapter module storage
        ModuleStorage storage ms = modulesStorage();
        uint256 length = ms.modules.length;
        modules = new IAdapter.Module[](length);
        for (uint256 i = 0; i < length; i++) {
            address module = ms.modules[i];
            bytes4[] memory selectors = ms.moduleToSelectors[module].selectors;
            modules[i] = IAdapter.Module(module, selectors);
        }
    }

    /*//////////////////////////////////////////////////////////////
                                 OWNER
    //////////////////////////////////////////////////////////////*/

    /// @notice Get the owner of the adapter
    /// @return owner The owner of the adapter
    function owner() internal view returns (address) {
        // Get adapter module storage
        ModuleStorage storage ms = modulesStorage();
        return ms.owner;
    }

    /// @notice Set the owner of the adapter
    /// @param _owner The new owner of the adapter
    function setOwner(address _owner) internal {
        // Get adapter module storage
        ModuleStorage storage ms = modulesStorage();
        // Cache the old owner
        address oldOwner = ms.owner;
        // Set the new owner
        ms.owner = _owner;
        // Emit the OwnershipTransferred event
        emit OwnershipTransferred(oldOwner, _owner);
    }

    /// @notice Check if the caller is the owner of the adapter, revert if not
    function isOwner() internal view {
        // Get adapter module storage
        ModuleStorage storage ms = modulesStorage();
        if (msg.sender != ms.owner) {
            revert UnauthorizedAccount(msg.sender);
        }
    }
}
"
    },
    "src/modules/libraries/ERC20UtilsLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";

/// @title ERC20 Utility Library
/// @dev Library with common functions used by different modules within the Portikus V2 protocol
library ERC20UtilsLib {
    /*//////////////////////////////////////////////////////////////
                                 ERRORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Emitted when the permit execution fails
    error PermitFailed();

    /*//////////////////////////////////////////////////////////////
                               CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @dev An address used to represent the native token
    address internal constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    /// @dev The address of the Permit2 contract
    address internal constant PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /*//////////////////////////////////////////////////////////////
                               LIBRARIES
    //////////////////////////////////////////////////////////////*/

    using SafeTransferLib for address;

    /*//////////////////////////////////////////////////////////////
                                TRANSFER
    //////////////////////////////////////////////////////////////*/

    /// @dev Transfer the dest token to the order beneficiary, reducing the amount by 1 wei for gas optimization
    ///      purposes. If the destToken is ETH, it will transfer native ETH to recipient
    /// @param destToken The address of the dest token
    /// @param recipient The address to transfer to
    /// @param amount The amount to transfer
    function transferTo(address destToken, address recipient, uint256 amount) internal {
        // If the destToken is ETH, transfer native ETH
        if (isEth(destToken)) {
            recipient.safeTransferETH(amount);
        } else {
            // Otherwise, transfer the dest token to the recipient, reducing the amount by 1 wei
            // for gas optimization purposes
            destToken.safeTransfer(recipient, amount);
        }
    }

    /// @dev Transfer the src token from the user to a recipient, using permit2 allowance or erc20 allowance based on
    ///      permit length, if the permit length is 192 or 1 it will call transferFrom using permit2
    ///      allowance, otherwise it will call transferFrom using erc20 allowance unless permit length is 96
    /// @param srcToken The address of the src token
    /// @param owner The owner of the token
    /// @param recipient The recipient of the token
    /// @param amount The amount to transfer
    /// @param permitLength The length of the permit
    function transferFrom(
        address srcToken,
        address owner,
        address recipient,
        uint256 amount,
        uint256 permitLength
    )
        internal
    {
        // Skip transferring if the permit length is 96 (permit2TransferFrom)
        if (permitLength == 96) {
            return;
        }
        // If the permit length is 192 or 1 execute permit2TransferFrom to transfer the
        // input assets from the owner to the agent, otherwise execute ERC20 transferFrom
        if (permitLength == 192 || permitLength == 1) {
            // Transfer the input assets from the owner to the recipient using permit2 allowance
            srcToken.permit2TransferFrom(owner, recipient, amount);
        } else {
            // Transfer the input assets from the owner to the recipient using the ERC20 transferFrom function
            srcToken.safeTransferFrom(owner, recipient, amount);
        }
    }

    /*//////////////////////////////////////////////////////////////
                                 PERMIT
    //////////////////////////////////////////////////////////////*/

    /// @dev Executes the permit function on the provided token, depending on the permit length,
    ///      it will call the EIP2612 permit (224), DAI-Style permit (256), Permit2 AllowanceTransfer (192)
    ///      or Permit2 Signature (96)
    /// @param token The address of the token
    /// @param data The permit data
    /// @param owner The owner of the token (used for Permit2)
    /// @param deadline The deadline for the permit (used for Permit2)
    /// @param amount The amount to permit (used for Permit2)
    function permit(
        address token,
        bytes calldata data,
        address owner,
        uint256 deadline,
        uint256 amount,
        address recipient
    )
        internal
    {
        // solhint-disable-next-line no-inline-assembly
        assembly ("memory-safe") {
            // check the permit length
            switch data.length
            // 0x00 = no permit
            case 0x00 {
            // do nothing
            }
            case 0x01 {
            // 0x01 is used to signify already existing permit2 allowance
            }
            // 32(permit2nonce) + 64(signature) = 96 Permit2 Transfer format
            case 0x60 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x30f28b7a00000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), token) // store the srcToken
                mstore(add(x, 0x24), amount) // store the amount
                calldatacopy(add(x, 0x44), data.offset, 0x20) // copy the nonce
                mstore(add(x, 0x64), deadline) // store the deadline
                mstore(add(x, 0x84), recipient) // store the recipient address (executor)
                mstore(add(x, 0xa4), amount) // store the amount
                mstore(add(x, 0xc4), owner) // store the owner
                mstore(add(x, 0xe4), 0x100) // store the offset
                mstore(add(x, 0x104), 0x40) // store the length
                calldatacopy(add(x, 0x124), add(data.offset, 0x20), 0x40) // copy the signature
                    // Call Permit2 contract and revert on failure
                if iszero(call(gas(), PERMIT2_ADDRESS, 0x00, x, 0x164, 0x00, 0x00)) {
                    mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the
                        // selector
                    revert(0x00, 0x04) // Revert with PermitFailed error
                }
            }
            // 32(amount) + 32(nonce) + 32(expiration) + 32(sigDeadline) + 64(signature) = 192 Permit2 allowance format
            case 0xc0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x2b67b57000000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), owner) // store the owner address
                mstore(add(x, 0x24), token) // store the token address
                calldatacopy(add(x, 0x44), data.offset, 0x60) // copy amount, expiration, nonce
                mstore(add(x, 0xa4), address()) // store this contract's address as the spender
                calldatacopy(add(x, 0xc4), add(data.offset, 0x60), 0x20) // copy sigDeadline
                mstore(add(x, 0xe4), 0x100) // store the offset for signature
                mstore(add(x, 0x104), 0x40) // store the length of signature
                calldatacopy(add(x, 0x124), add(data.offset, 0x80), 0x40) // copy the signature
                    // Call Permit2 contract and revert on failure
                if iszero(call(gas(), PERMIT2_ADDRESS, 0x00, x, 0x164, 0x00, 0x00)) {
                    mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the
                        // selector
                    revert(0x00, 0x04) // Revert with PermitFailed error
                }
            }
            // 32 * 7 = 224 EIP2612 Permit
            case 0xe0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0xd505accf00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0xe0) // store the args
                pop(call(gas(), token, 0x00, x, 0xe4, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // 32 * 8 = 256 DAI-Style Permit
            case 0x100 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x8fcbaf0c00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0x100) // store the args
                pop(call(gas(), token, 0x00, x, 0x104, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // Otherwise revert
            default {
                mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the selector
                revert(0x00, 0x04) // Revert with PermitFailed error
            }
        }
    }

    /// @dev A fillable version of the permit function from this lib, that doesn't support Permit2 SignatureTransfer
    /// @param token The address of the token
    /// @param data The permit data
    /// @param owner The owner of the token (used for Permit2)
    function permit(address token, bytes calldata data, address owner) internal {
        // solhint-disable-next-line no-inline-assembly
        assembly ("memory-safe") {
            // check the permit length
            switch data.length
            // 0x00 = no permit
            case 0x00 {
            // do nothing
            }
            case 0x01 {
            // 0x01 is used to signify already existing permit2 allowance
            }
            // 32(amount) + 32(nonce) + 32(expiration) + 32(sigDeadline) + 64(signature) = 192 Permit2 allowance format
            case 0xc0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x2b67b57000000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), owner) // store the owner address
                mstore(add(x, 0x24), token) // store the token address
                calldatacopy(add(x, 0x44), data.offset, 0x60) // copy amount, expiration, nonce
                mstore(add(x, 0xa4), address()) // store this contract's address as the spender
                calldatacopy(add(x, 0xc4), add(data.offset, 0x60), 0x20) // copy sigDeadline
                mstore(add(x, 0xe4), 0x100) // store the offset for signature
                mstore(add(x, 0x104), 0x40) // store the length of signature
                calldatacopy(add(x, 0x124), add(data.offset, 0x80), 0x40) // copy the signature
                    // Call Permit2 contract and revert on failure
                if iszero(call(gas(), PERMIT2_ADDRESS, 0x00, x, 0x164, 0x00, 0x00)) {
                    mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the
                        // selector
                    revert(0x00, 0x04) // Revert with PermitFailed error
                }
            }
            // 32 * 7 = 224 EIP2612 Permit
            case 0xe0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0xd505accf00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0xe0) // store the args
                pop(call(gas(), token, 0x00, x, 0xe4, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // 32 * 8 = 256 DAI-Style Permit
            case 0x100 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x8fcbaf0c00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0x100) // store the args
                pop(call(gas(), token, 0x00, x, 0x104, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // Otherwise revert
            default {
                mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the selector
                revert(0x00, 0x04) // Revert with PermitFailed error
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                                UTILITIES
    //////////////////////////////////////////////////////////////*/

    /// @notice Checks if a token is ETH
    /// @param token The token address to check
    /// @return True if the token represents ETH
    function isEth(address token) internal pure returns (bool) {
        return token == ETH_ADDRESS;
    }

    /*//////////////////////////////////////////////////////////////
                                BALANCE
    //////////////////////////////////////////////////////////////*/

    /// @dev Returns the balance of address(this), works for both ETH and ERC20 tokens
    function getBalance(address token) internal view returns (uint256 balanceOf) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            switch eq(token, ETH_ADDRESS)
            // ETH
            case 0x01 { balanceOf := selfbalance() }
            // ERC20
            default {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x70a0823100000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), address()) // store the account
                let success := staticcall(gas(), token, x, 0x24, x, 0x20) // call balanceOf
                if success { balanceOf := mload(x)} // load the balance
            }
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {StorageSlot} from "./StorageSlot.sol";

/**
 * @dev Variant of {ReentrancyGuard} that uses transient storage.
 *
 * NOTE: This variant only works on networks where EIP-1153 is available.
 */
abstract contract ReentrancyGuardTransient {
    using StorageSlot for *;

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant REENTRANCY_GUARD_STORAGE =
        0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        REENTRANCY_GUARD_STORAGE.asBoolean().tstore(true);
    }

    function _nonReentrantAfter() private {
        REENTRANCY_GUARD_STORAGE.asBoolean().tstore(false);
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return REENTRANCY_GUARD_STORAGE.asBoolean().tload();
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ShortStrings.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol)

pragma solidity ^0.8.20;

import {StorageSlot} from "./StorageSlot.sol";

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as an immutable variable.
 *
 * Strings of arbitrary length can be optimized using this library if
 * they are short enough (up to 31 bytes) by packing them with their
 * length (1 byte) in a single EVM word (32 bytes). Additionally, a
 * fallback mechanism can be used for every other case.
 *
 * Usage example:
 *
 * ```solidity
 * contract Named {
 *     using ShortStrings for *;
 *
 *     ShortString private immutable _name;
 *     string private _nameFallback;
 *
 *     constructor(string memory contractName) {
 *         _name = contractName.toShortStringWithFallback(_nameFallback);
 *     }
 *
 *     function name() external view returns (string memory) {
 *         return _name.toStringWithFallback(_nameFallback);
 *     }
 * }
 * ```
 */
library ShortStrings {
    // Used as an identifier for strings longer than 31 bytes.
    bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;

    error StringTooLong(string str);
    error InvalidShortString();

    /**
     * @dev Encode a string of at most 31 chars into a `ShortString`.
     *
     * This will trigger a `StringTooLong` error is the input string is too long.
     */
    function toShortString(string memory str) internal pure returns (ShortString) {
        bytes memory bstr = bytes(str);
        if (bstr.length > 31) {
            revert StringTooLong(str);
        }
        return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
    }

    /**
     * @dev Decode a `ShortString` back to a "normal" string.
     */
    function toString(ShortString sstr) internal pure returns (string memory) {
        uint256 len = byteLength(sstr);
        // using `new string(len)` would work locally but is not memory safe.
        string memory str = new string(32);
        /// @solidity memory-safe-assembly
        assembly {
            mstore(str, len)
            mstore(add(str, 0x20), sstr)
        }
        return str;
    }

    /**
     * @dev Return the length of a `ShortString`.
     */
    function byteLength(ShortString sstr) internal pure returns (uint256) {
        uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
        if (result > 31) {
            revert InvalidShortString();
        }
        return result;
    }

    /**
     * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
     */
    function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
        if (bytes(value).length < 32) {
            return toShortString(value);
        } else {
            StorageSlot.getStringSlot(store).value = value;
            return ShortString.wrap(FALLBACK_SENTINEL);
        }
    }

    /**
     * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     */
    function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return toString(value);
        } else {
            return store;
        }
    }

    /**
     * @dev Return the length of a string that was encoded to `ShortString` or written to storage using
     * {setWithFallback}.
     *
     * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
     * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
     */
    function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return byteLength(value);
        } else {
            return bytes(store).length;
        }
    }
}
"
    },
    "src/adapter/interfaces/IAdapter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Interfaces
import { IErrors } from "./IErrors.sol";
import { IERC173 } from "./IERC173.sol";

/// @notice Interface for Portikus V2 adapters
interface IAdapter is IErrors, IERC173 {
    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    //////////////////////////////////////////////////////////////*/

    /// @notice The module struct contains the address of the module and its selectors
    struct Module {
        address module;
        bytes4[] selectors;
    }

    /*//////////////////////////////////////////////////////////////
                                INSTALL
    //////////////////////////////////////////////////////////////*/

    /// @notice Add a new module to the adapter, the module must be registered in the Portikus registry
    /// @param module The address of the module to install
    function install(address module) external;

    /*//////////////////////////////////////////////////////////////
                               UNINSTALL
    //////////////////////////////////////////////////////////////*/

    /// @notice Remove a previously installed module from the adapter
    /// @param module The address of the module to uninstall
    function uninstall(address module) external;

    /*//////////////////////////////////////////////////////////////
                                GETTERS
    //////////////////////////////////////////////////////////////*/

    /// @notice Gets all installed modules and their selectors
    /// @return modules The installed modules and their selectors
    function getModules() external view returns (Module[] memory modules);
}
"
    },
    "lib/solady/src/utils/SafeTransferLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /// @dev The Permit2 operation has failed.
    error Permit2Failed();

    /// @dev The Permit2 amount must be less than `2**160 - 1`.
    error Permit2AmountOverflow();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
    uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;

    /// @dev The unique EIP-712 domain domain separator for the DAI token contract.
    bytes32 internal constant DAI_DOMAIN_SEPARATOR =
        0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;

    /// @dev The address for the WETH9 contract on Ethereum mainnet.
    address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    /// @dev The canonical Permit2 address.
    /// [Github](https://github.com/Uniswap/permit2)
    /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
    address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
    //
    // The regular variants:
    // - Forwards all remaining gas to the target.
    // - Reverts if the target reverts.
    // - Reverts if the current contract has insufficient balance.
    //
    // The force variants:
    // - Forwards with an optional gas stipend
    //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
    // - If the target reverts, or if the gas stipend is exhausted,
    //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
    //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
    // - Reverts if the current contract has insufficient balance.
    //
    // The try variants:
    // - Forwards with a mandatory gas stipend.
    // - Instead of reverting, returns whether the transfer succeeded.

    /// @dev Sends `amount` (in wei) ETH to `to`.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`.
    function safeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer all the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function trySafeTransferAllETH(address to, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function trySafeTransferFrom(address token, address from, address to, uint256 amount)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
            amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, to) // Store the `to` argument.
            amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
    /// then retries the approval again (some tokens, e.g. USDT, requires this).
    /// Reverts upon failure.
    function safeApproveWithRetry(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, retrying upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x34, 0) // Store 0 for the `amount`.
                    mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                    pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                    mstore(0x34, amount) // Store back the original `amount`.
                    // Retry the approval, reverting upon failure.
                    success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                    if iszero(and(eq(mload(0x00), 1), success)) {
                        // Check the `extcodesize` again just in case the token selfdestructs lol.
                        if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                            mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                            revert(0x1c, 0x04)
                        }
                    }
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            amount :=
                mul( // The arguments of `mul` are evaluated from right to left.
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// If the initial attempt fails, try to use Permit2 to transfer the token.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
        if (!trySafeTransferFrom(token, from, to, amount)) {
            permit2TransferFrom(token, from, to, amount);
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
    /// Reverts upon failure.
    function permit2TransferFrom(address token, address from, address to, uint256 amount)
        internal
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(m, 0x74), shr(96, shl(96, token)))
            mstore(add(m, 0x54), amount)
            mstore(add(m, 0x34), to)
            mstore(add(m, 0x20), shl(96, from))
            // `transferFrom(address,address,uint160,address)`.
            mstore(m, 0x36c78516000000000000000000000000)
            let p := PERMIT2
            let exists := eq(chainid(), 1)
            if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
            if iszero(
                and(
                    call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
                    lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
                )
            ) {
                mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
                revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
            }
        }
    }

    /// @dev Permit a user to spend a given amount of
    /// another user's tokens via native EIP-2612 permit if possible, falling
    /// back to Permit2 if native permit fails or is not implemented on the token.
    function permit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        bool success;
        /// @solidity memory-safe-assembly
        assembly {
            for {} shl(96, xor(token, WETH9)) {} {
                mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
                if iszero(
                    and( // The arguments of `and` are evaluated from right to left.
                        lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
                        // Gas stipend to limit gas burn for tokens that don't refund gas when
                        // an non-existing function is called. 5K should be enough for a SLOAD.
                        staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
                    )
                ) { break }
                // After here, we can be sure that token is a contract.
                let m := mload(0x40)
                mstore(add(m, 0x34), spender)
                mstore(add(m, 0x20), shl(96, owner))
                mstore(add(m, 0x74), deadline)
                if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
                    mstore(0x14, owner)
                    mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
                    mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
                    mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
                    // `nonces` is already at `add(m, 0x54)`.
                    // `1` is already stored at `add(m, 0x94)`.
                    mstore(add(m, 0xb4), and(0xff, v))
                    mstore(add(m, 0xd4), r)
                    mstore(add(m, 0xf4), s)
                    success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
                    break
                }
                mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
                mstore(add(m, 0x54), amount)
                mstore(add(m, 0x94), and(0xff, v))
                mstore(add(m, 0xb4), r)
                mstore(add(m, 0xd4), s)
                success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
                break
            }
        }
        if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
    }

    /// @dev Simple permit on the Permit2 contract.
    function simplePermit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(m, 0x927da105) // `allowance(address,address,address)`.
            {
                let addressMask := shr(96, not(0))
                mstore(add(m, 0x20), and(addressMask, owner))
                mstore(add(m, 0x40), and(addressMask, token))
                mstore(add(m, 0x60), and(addressMask, spender))
                mstore(add(m, 0xc0), and(addressMask, spender))
            }
            let p := mul(PERMIT2, iszero(shr(160, amount)))
            if iszero(
                and( // The arguments of `and` are evaluated

Tags:
Proxy, Upgradeable, Factory|addr:0x084693c47396e91c1e4ed18b35e2d6328263d8b3|verified:true|block:23697551|tx:0xccff3b7372239581dffd552ab3c223c290556b173e168629388b6c90c78ecaa9|first_check:1761916306

Submitted on: 2025-10-31 14:11:46

Comments

Log in to comment.

No comments yet.