Exchange

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/exchange/Exchange.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";

import {CallExecutor} from "src/libraries/CallExecutor.sol";
import {TokenHelpers, Constants} from "src/utils/TokenHelpers.sol";
import {IReferralFeeVault, UpdateInfo} from "src/vaults/interfaces/IReferralFeeVault.sol";

import {OrderValidator, Order, ReferralFee} from "./libraries/OrderValidator.sol";
import {IExchange, Fee} from "./interfaces/IExchange.sol";

/**
 * @title Exchange
 * @notice A decentralized exchange contract that enables order execution through trusted executors
 * @dev This contract implements:
 * - Order execution with fee management
 * - Access control for administrative functions
 * - Pausable functionality for emergency stops
 * - Reentrancy protection
 * - Asset sweeping for accidentally sent tokens
 */
contract Exchange is IExchange, AccessControl, Pausable, ReentrancyGuardTransient {
    using OrderValidator for Order;

    /// @notice Role identifier for admin functions
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    /// @notice Address of the authorized operator for order execution with fee
    /// @dev The smart contract trusts the order data that the operator signs (primarily commissions)
    address private _operator;

    /// @notice Mapping to track trusted executor addresses
    mapping(address executor => bool isTrusted) private _trustedExecutors;

    /// @notice Mapping to track sender nonces
    mapping(address sender => uint256 nonce) private _nonces;

    /// @notice Current fee configuration
    Fee private _fee;

    /**
     * @notice Initializes the exchange
     * @param defaultAdmin The default admin address for role management
     * @param operator The operator address for order execution
     * @param fee The fee configuration
     * @param trustedExecutors List of the trusted executors which integrate dexes
     * @dev Can only be called once during proxy initialization
     */
    constructor(address defaultAdmin, address operator, Fee memory fee, address[] memory trustedExecutors) {
        if (defaultAdmin == address(0)) {
            revert ZeroAddress();
        }

        _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);

        _setOperator(operator);
        _setFee(fee);
        _setTrustedExecutors(trustedExecutors, true);
    }

    // region - Fill order -

    /**
     * @notice Executes a user order
     * @param order The user order
     * @param signature The EIP-712 signature of the order by the operator account
     * @param executor The trusted executor contract address for order execution
     * @param executorData The execution data for the executor contract
     *
     * @dev Only when not paused
     */
    function fillOrder(Order calldata order, bytes calldata signature, address executor, bytes calldata executorData)
        external
        payable
        nonReentrant
        whenNotPaused
        returns (uint256 actualAmountOut)
    {
        _validateOrderExecution(order, signature, executor, executorData);

        TokenHelpers.transferFrom(order.tokenIn, msg.sender, address(this), order.amountIn);
        actualAmountOut = _fillOrder(order, executor, executorData);
        TokenHelpers.transfer(order.tokenOut, msg.sender, actualAmountOut);
    }

    function _fillOrder(Order memory order, address executor, bytes calldata executorData)
        private
        returns (uint256 actualAmountOut)
    {
        uint256 feeAmountIn = _takeFee(order.tokenIn, order.amountIn, order.referralFees);
        if (feeAmountIn > 0) {
            order.amountIn = order.amountIn - feeAmountIn;
        }

        actualAmountOut = _execute(order, executor, executorData);

        uint256 feeAmountOut = _takeFee(order.tokenOut, actualAmountOut, order.referralFees);
        if (feeAmountOut > 0) {
            actualAmountOut = actualAmountOut - feeAmountOut;
        }

        emit OrderFilled(msg.sender, order, actualAmountOut);
    }

    function _execute(Order memory order, address executor, bytes calldata executorData)
        private
        returns (uint256 actualAmountOut)
    {
        uint256 tokenInBalanceBefore = TokenHelpers.getBalance(order.tokenIn, address(this));
        uint256 tokenOutBalanceBefore = TokenHelpers.getBalance(order.tokenOut, address(this));

        CallExecutor.delegateCall(executor, executorData);

        uint256 tokenInBalanceAfter = TokenHelpers.getBalance(order.tokenIn, address(this));
        uint256 tokenOutBalanceAfter = TokenHelpers.getBalance(order.tokenOut, address(this));

        uint256 actualAmountIn = tokenInBalanceBefore - tokenInBalanceAfter;

        if (actualAmountIn != order.amountIn) {
            revert InsufficientInputAmount(order.amountIn, actualAmountIn);
        }

        actualAmountOut = tokenOutBalanceAfter - tokenOutBalanceBefore;
        if (actualAmountOut < order.minAmountOut) {
            revert InsufficientOutputAmount(order.minAmountOut, actualAmountOut);
        }
    }

    function _validateOrderExecution(
        Order calldata order,
        bytes calldata signature,
        address executor,
        bytes calldata executorData
    ) private {
        if (executor == address(0)) {
            revert ZeroAddress();
        }

        if (!_trustedExecutors[executor]) {
            revert UntrustedExecutor(executor);
        }

        if (executorData.length == 0) {
            revert ZeroData();
        }

        order.validate(signature, _operator, _nonces);
    }

    function _takeFee(address token, uint256 amount, ReferralFee[] memory referralFees)
        private
        returns (uint256 protocolFeeAmount)
    {
        Fee memory fee = _fee;

        if (token == fee.token && fee.protocolFeeValue > 0) {
            protocolFeeAmount = _calculateFeeAmount(amount, fee.protocolFeeValue);

            uint256 numberOfReferralFees = referralFees.length;
            uint256 sumOfReferralFeeAmount = 0;
            if (numberOfReferralFees > 0) {
                UpdateInfo[] memory updates = new UpdateInfo[](numberOfReferralFees);

                for (uint256 i = 0; i < numberOfReferralFees; i++) {
                    ReferralFee memory referralFee = referralFees[i];

                    uint256 referralFeeAmount = _calculateFeeAmount(protocolFeeAmount, referralFee.value);
                    sumOfReferralFeeAmount += referralFeeAmount;

                    updates[i] = UpdateInfo({uuid: referralFee.uuid, amount: referralFeeAmount, token: token});
                }

                IReferralFeeVault(fee.referralRecipient).updateBalances(updates);
                TokenHelpers.transfer(token, fee.referralRecipient, sumOfReferralFeeAmount);
            }

            TokenHelpers.transfer(token, fee.protocolRecipient, protocolFeeAmount - sumOfReferralFeeAmount);
        }
    }

    function _calculateFeeAmount(uint256 amount, uint256 feeValue) private pure returns (uint256 feeAmount) {
        return (amount * feeValue) / Constants.FEE_BASIS_POINTS;
    }

    // endregion

    // region - Pausable -

    /**
     * @notice Pauses the exchange operations
     * @dev Only ADMIN_ROLE can pause
     */
    function pause() external onlyRole(ADMIN_ROLE) {
        _pause();
    }

    /**
     * @notice Unpauses the exchange operations
     * @dev Only ADMIN_ROLE can unpause
     */
    function unpause() external onlyRole(ADMIN_ROLE) {
        _unpause();
    }

    /**
     * @notice Checks if the exchange is paused
     * @return True if paused, false otherwise
     */
    function isPaused() external view returns (bool) {
        return paused();
    }

    // endregion

    // region - Operator control -

    /**
     * @notice Returns the current operator address
     * @return The operator address
     */
    function getOperator() external view returns (address) {
        return _operator;
    }

    /**
     * @notice Sets the operator address
     * @param operator The new operator address
     */
    function setOperator(address operator) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _setOperator(operator);
    }

    function _setOperator(address operator) private {
        if (operator == address(0)) {
            revert ZeroAddress();
        }

        _operator = operator;

        emit OperatorSet(operator);
    }

    // endregion

    // region - Set Fee -

    /**
     * @notice Sets the fee configuration
     * @param fee The new fee configuration
     *
     * @dev Only DEFAULT_ADMIN_ROLE can set fee configuration
     */
    function setFee(Fee memory fee) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _setFee(fee);
    }

    /**
     * @notice Returns the current fee configuration
     * @return The fee configuration
     */
    function getFee() external view returns (Fee memory) {
        return _fee;
    }

    function _setFee(Fee memory fee) private {
        if (fee.protocolRecipient == address(0) || fee.referralRecipient == address(0)) {
            revert ZeroAddress();
        }

        if (fee.protocolFeeValue > Constants.MAX_PROTOCOL_FEE_VALUE) {
            revert InvalidProtocolFeeValue(fee.protocolFeeValue);
        }

        _fee = fee;

        emit FeeSet(fee);
    }

    // endregion

    // region - Control of trusted operators -

    /**
     * @notice Sets list of the trusted executors
     * @param executors List of the trusted executor addresses
     * @param isTrusted True, if the executor is set, else - false
     *
     * @dev Only DEFAULT_ADMIN_ROLE can set trusted executors
     * @dev An executor is a contract that the exchange can delegatecall to for execution
     */
    function setTrustedExecutors(address[] calldata executors, bool isTrusted) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _setTrustedExecutors(executors, isTrusted);
    }

    /**
     * @notice Checks if an executor is trusted
     * @param executor The executor address to check
     */
    function isExecutorTrusted(address executor) external view returns (bool) {
        return _trustedExecutors[executor];
    }

    function _setTrustedExecutors(address[] memory executors, bool isTrusted) private {
        uint256 numberOfExecutors = executors.length;

        if (numberOfExecutors == 0) {
            revert ZeroNumberOfExecutors();
        }

        for (uint256 i = 0; i < numberOfExecutors; i++) {
            address executor = executors[i];

            if (executor == address(0)) {
                revert ZeroAddress();
            }

            _trustedExecutors[executor] = isTrusted;
        }

        emit TrustedExecutorsSet(executors, isTrusted);
    }

    // endregion

    // region - Utility functions -

    /**
     * @notice Returns the nonce of the specific account
     * @param account The account address
     *
     * @dev Required for signature generation. Allows the signature to be used once
     */
    function getNonce(address account) external view returns (uint256) {
        return _nonces[account];
    }

    /**
     * @notice Sweep assets
     * @param token Address of the token
     * @param amount Token amount
     *
     * @dev Use a zero address for the token param to withdraw native currency
     * @dev Allows to withdraw assets that were sent to the contract by mistake
     */
    function sweep(address token, uint256 amount) external onlyRole(ADMIN_ROLE) {
        TokenHelpers.transfer(token, msg.sender, amount);

        emit Swept(token, msg.sender, amount);
    }

    /**
     * @notice Allows the exchange to receive ETH
     * @dev This function enables the contract to accept ETH transfers, which are necessary for transfers from dex
     */
    receive() external payable whenNotPaused {}

    // endregion
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {IERC165, ERC165} from "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    mapping(bytes32 role => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /// @inheritdoc IERC165
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        return _roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        if (!hasRole(role, account)) {
            _roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        if (hasRole(role, account)) {
            _roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Pausable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    bool private _paused;

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/ReentrancyGuardTransient.sol)

pragma solidity ^0.8.24;

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

/**
 * @dev Variant of {ReentrancyGuard} that uses transient storage.
 *
 * NOTE: This variant only works on networks where EIP-1153 is available.
 *
 * _Available since v5.1._
 */
abstract contract ReentrancyGuardTransient {
    using TransientSlot 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, REENTRANCY_GUARD_STORAGE.asBoolean().tload() will be false
        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();
    }
}
"
    },
    "src/libraries/CallExecutor.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/**
 * @title CallExecutor
 * @notice A library for executing delegate calls to external executor contracts
 * @dev This library provides a safe way to execute delegate calls with proper error handling
 *
 * This is used by the Exchange contract to execute orders through external executors
 * like DEX aggregators or trading protocols
 */
library CallExecutor {
    /**
     * @notice Executes a delegate call to an executor contract
     * @param executor The address of the executor contract to call
     * @param data The calldata to send to the executor
     * @dev Uses delegatecall to execute code in the context of the calling contract
     */
    function delegateCall(address executor, bytes memory data) internal {
        assembly {
            let result := delegatecall(gas(), executor, add(data, 0x20), mload(data), 0, 0)
            returndatacopy(0, 0, returndatasize())

            if iszero(result) { revert(0, returndatasize()) }
        }
    }
}
"
    },
    "src/utils/TokenHelpers.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Constants} from "./Constants.sol";

/**
 * @title TokenHelpers
 * @notice Utility library for handling both ERC20 tokens and native currency
 */
library TokenHelpers {
    using SafeERC20 for IERC20;

    /// @notice Thrown when account has insufficient balance for transfer
    error InsufficientAmount(uint256 balance, uint256 needed);

    /// @notice Thrown when account has invalid balance for transfer from
    error InvalidAmount(uint256 balance, uint256 needed);

    /// @notice Thrown when native currency transfer fails
    error TransferFailed(address token, uint256 amount);

    /**
     * @notice Checks if the address represents native currency
     * @param token The token address to check
     * @return True if token is native currency, false otherwise
     */
    function isETH(address token) internal pure returns (bool) {
        if (token == Constants.NATIVE_CURRENCY) {
            return true;
        }

        return false;
    }

    /**
     * @notice Gets the balance of tokens for a given account
     * @param token The token address (0x0 for native currency)
     * @param account The account address to check balance for
     * @return The balance of tokens for the account
     */
    function getBalance(address token, address account) internal view returns (uint256) {
        if (isETH(token)) {
            return account.balance;
        }

        return IERC20(token).balanceOf(account);
    }

    /**
     * @notice Transfers tokens from the current contract to a recipient
     * @param token The token address (0x0 for native currency)
     * @param to The recipient address
     * @param amount The amount to transfer
     */
    function transfer(address token, address to, uint256 amount) internal {
        if (amount > 0) {
            if ((isETH(token))) {
                uint256 balance = address(this).balance;

                if (balance < amount) {
                    revert InsufficientAmount(balance, amount);
                }

                (bool success,) = to.call{value: amount}("");
                if (!success) {
                    revert TransferFailed(token, amount);
                }
            } else {
                IERC20(token).safeTransfer(to, amount);
            }
        }
    }

    /**
     * @notice Transfers tokens from one account to another
     * @param token The token address (0x0 for native currency)
     * @param from The sender address
     * @param to The recipient address
     * @param amount The amount to transfer
     */
    function transferFrom(address token, address from, address to, uint256 amount) internal {
        if (amount > 0) {
            if (isETH(token)) {
                if (msg.value != amount) {
                    revert InvalidAmount(msg.value, amount);
                }
            } else {
                IERC20(token).safeTransferFrom(from, to, amount);
            }
        }
    }
}
"
    },
    "src/vaults/interfaces/IReferralFeeVault.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {ClaimInfo} from "../libraries/ClaimValidator.sol";

struct UpdateInfo {
    uint256 uuid;
    uint256 amount;
    address token;
}

/**
 * @title IReferralFeeVault
 * @notice Interface for managing referral fee distribution
 */
interface IReferralFeeVault {
    /// @notice Emitted when operator is set or updated
    event OperatorSet(address operator);

    /// @notice Emitted when balance is updated
    event BalanceUpdated(UpdateInfo update);

    /// @notice Emitted when rewards is claimed
    event Claimed(address indexed sender, ClaimInfo claimInfo);

    /// @notice Emitted when token is swept from exchange
    event Swept(address indexed token, address indexed sender, uint256 amount);

    /// @notice Thrown when address is zero
    error ZeroAddress();

    /// @notice Thrown when referral uuid is zero
    error ZeroUuid();

    /// @notice Thrown when referral amount is zero
    error ZeroAmount();

    function updateBalance(UpdateInfo calldata update) external;
    function updateBalances(UpdateInfo[] calldata updates) external;
    function getBalance(uint256 uuid, address token) external view returns (uint256);
    function claim(ClaimInfo calldata claimInfo, bytes calldata signature) external;
    function pause() external;
    function unpause() external;
    function isPaused() external view returns (bool);
    function getOperator() external view returns (address);
    function setOperator(address operator) external;
    function getNonce(uint256 uuid) external view returns (uint256);
    function sweep(address token, uint256 amount) external;
}
"
    },
    "src/exchange/libraries/OrderValidator.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {Constants} from "src/utils/Constants.sol";

struct ReferralFee {
    uint256 uuid;
    uint256 value; // in basis points (e.g., 1_000 = 1%)
}

struct Order {
    address tokenIn;
    address tokenOut;
    uint256 amountIn;
    uint256 minAmountOut;
    ReferralFee[] referralFees;
}

library OrderValidator {
    bytes32 public constant REFERRAL_FEE_TYPEHASH =
        keccak256(abi.encodePacked("ReferralFee(uint256 uuid,uint256 value)"));

    bytes32 public constant ORDER_TYPEHASH = keccak256(
        abi.encodePacked(
            "Order(address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,ReferralFee[] referralFees)ReferralFee(uint256 uuid,uint256 value)"
        )
    );

    bytes32 public constant OPERATOR_SIGNATURE_TYPEHASH = keccak256(
        abi.encodePacked(
            "OperatorSignature(Order order,address user,uint256 nonce)Order(address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,ReferralFee[] referralFees)ReferralFee(uint256 uuid,uint256 value)"
        )
    );

    /// @notice Thrown when amount is zero
    error ZeroAmount();

    /// @notice Thrown when referral uuid is zero
    error ZeroUuid();

    /// @notice Thrown when referral value is zero
    error ZeroValue();

    /// @notice Thrown when input and output tokens are the same
    error InvalidTokens();

    /// @notice Thrown when order signature is invalid
    error InvalidOrderSignature();

    /// @notice Thrown when sum of referral fee value more than max referral fee value
    error InvalidSumOfReferralFeeValue(uint256 sumOfReferralFeeValue);

    /// @notice Thrown when number of referral fees in order more than max
    error TooManyReferralFees();

    function validate(
        Order calldata order,
        bytes calldata signature,
        address operator,
        mapping(address => uint256) storage nonces
    ) internal {
        if (order.tokenIn == order.tokenOut) {
            revert InvalidTokens();
        }

        if (order.amountIn == 0) {
            revert ZeroAmount();
        }

        _validateReferralFees(order.referralFees);

        bool isValid = _isValidSignature(order, signature, operator, ++nonces[msg.sender]);
        if (!isValid) {
            revert InvalidOrderSignature();
        }
    }

    function _validateReferralFees(ReferralFee[] calldata referralFees) private pure {
        if (referralFees.length > Constants.MAX_REFERRAL_FEES_PER_ORDER) {
            revert TooManyReferralFees();
        }

        uint256 sumOfReferralFeeValue = 0;
        for (uint256 i = 0; i < referralFees.length; i++) {
            ReferralFee memory referralFee = referralFees[i];

            if (referralFee.uuid == 0) {
                revert ZeroUuid();
            }

            if (referralFee.value == 0) {
                revert ZeroValue();
            }

            sumOfReferralFeeValue += referralFee.value;
        }

        if (sumOfReferralFeeValue > Constants.MAX_REFERRAL_FEE_VALUE) {
            revert InvalidSumOfReferralFeeValue(sumOfReferralFeeValue);
        }
    }

    function _isValidSignature(Order calldata order, bytes calldata signature, address operator, uint256 nonce)
        private
        view
        returns (bool)
    {
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _domainSeparator(), _getDigest(order, nonce)));
        return ECDSA.recover(digest, signature) == operator;
    }

    function _domainSeparator() private view returns (bytes32) {
        uint256 chainId;

        assembly {
            chainId := chainid()
        }

        return keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256("Jupbot-Exchange"),
                keccak256("1"),
                chainId,
                address(this)
            )
        );
    }

    function _getDigest(Order calldata order, uint256 nonce) private view returns (bytes32) {
        bytes32[] memory referralFeesHashes = new bytes32[](order.referralFees.length);
        for (uint256 i = 0; i < order.referralFees.length; i++) {
            referralFeesHashes[i] =
                keccak256(abi.encode(REFERRAL_FEE_TYPEHASH, order.referralFees[i].uuid, order.referralFees[i].value));
        }

        bytes32 orderHash = keccak256(
            abi.encode(
                ORDER_TYPEHASH,
                order.tokenIn,
                order.tokenOut,
                order.amountIn,
                order.minAmountOut,
                keccak256(abi.encodePacked(referralFeesHashes))
            )
        );

        return keccak256(abi.encode(OPERATOR_SIGNATURE_TYPEHASH, orderHash, msg.sender, nonce));
    }
}
"
    },
    "src/exchange/interfaces/IExchange.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {Order} from "src/exchange/libraries/OrderValidator.sol";

struct Fee {
    address protocolRecipient;
    address referralRecipient;
    address token;
    uint256 protocolFeeValue; // in basis points (e.g., 1_000 = 1%)
}

interface IExchange {
    /// @notice Emitted when operator is set or updated
    event OperatorSet(address operator);

    /// @notice Emitted when trusted executors is set or updated
    event TrustedExecutorsSet(address[] executors, bool isTrusted);

    /// @notice Emitted when fee configuration is set or updated
    event FeeSet(Fee fee);

    /// @notice Emitted when an order is successfully filled
    event OrderFilled(address indexed sender, Order order, uint256 actualAmountOut);

    /// @notice Emitted when token is swept from exchange
    event Swept(address indexed token, address indexed sender, uint256 amount);

    /// @notice Thrown when address is zero
    error ZeroAddress();

    /// @notice Thrown when data is empty
    error ZeroData();

    /// @notice Thrown when trying to set zero number of executors
    error ZeroNumberOfExecutors();

    /// @notice Thrown when actual input amount is not equal order amountIn
    error InsufficientInputAmount(uint256 amountIn, uint256 actualAmountIn);

    /// @notice Thrown when executor is not trusted
    error UntrustedExecutor(address executor);

    /// @notice Thrown when actual output amount is less than minimum required
    error InsufficientOutputAmount(uint256 minAmountOut, uint256 actualAmountOut);

    /// @notice Thrown when invalid protocol fee value is provided
    error InvalidProtocolFeeValue(uint256 protocolFeeValue);

    function fillOrder(Order calldata order, bytes calldata signature, address executor, bytes calldata executorData)
        external
        payable
        returns (uint256 actualAmountOut);
    function pause() external;
    function unpause() external;
    function isPaused() external view returns (bool);
    function getOperator() external view returns (address);
    function setOperator(address operator) external;
    function setFee(Fee memory fee) external;
    function getFee() external view returns (Fee memory);
    function setTrustedExecutors(address[] calldata executors, bool isTrusted) external;
    function isExecutorTrusted(address executor) external view returns (bool);
    function getNonce(address account) external view returns (uint256);
    function sweep(address token, uint256 amount) external;
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/IAccessControl.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (access/IAccessControl.sol)

pragma solidity >=0.8.4;

/**
 * @dev External interface of AccessControl declared to support ERC-165 detection.
 */
interface IAccessControl {
    /**
     * @dev The `account` is missing a role.
     */
    error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);

    /**
     * @dev The caller of a function is not the expected one.
     *
     * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
     */
    error AccessControlBadConfirmation();

    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted to signal this.
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
     * Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     */
    function renounceRole(bytes32 role, address callerConfirmation) external;
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /// @inheritdoc IERC165
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/TransientSlot.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/TransientSlot.sol)
// This file was procedurally generated from scripts/generate/templates/TransientSlot.js.

pragma solidity ^0.8.24;

/**
 * @dev Library for reading and writing value-types to specific transient storage slots.
 *
 * Transient slots are often used to store temporary values that are removed after the current transaction.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 *  * Example reading and writing values using transient storage:
 * ```solidity
 * contract Lock {
 *     using TransientSlot for *;
 *
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
 *
 *     modifier locked() {
 *         require(!_LOCK_SLOT.asBoolean().tload());
 *
 *         _LOCK_SLOT.asBoolean().tstore(true);
 *         _;
 *         _LOCK_SLOT.asBoolean().tstore(false);
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library TransientSlot {
    /**
     * @dev UDVT that represents a slot holding an address.
     */
    type AddressSlot is bytes32;

    /**
     * @dev Cast an arbitrary slot to a AddressSlot.
     */
    function asAddress(bytes32 slot) internal pure returns (AddressSlot) {
        return AddressSlot.wrap(slot);
    }

    /**
     * @dev UDVT that represents a slot holding a bool.
     */
    type BooleanSlot is bytes32;

    /**
     * @dev Cast an arbitrary slot to a BooleanSlot.
     */
    function asBoolean(bytes32 slot) internal pure returns (BooleanSlot) {
        return BooleanSlot.wrap(slot);
    }

    /**
     * @dev UDVT that represents a slot holding a bytes32.
     */
    type Bytes32Slot is bytes32;

    /**
     * @dev Cast an arbitrary slot to a Bytes32Slot.
     */
    function asBytes32(bytes32 slot) internal pure returns (Bytes32Slot) {
        return Bytes32Slot.wrap(slot);
    }

    /**
     * @dev UDVT that represents a slot holding a uint256.
     */
    type Uint256Slot is bytes32;

    /**
     * @dev Cast an arbitrary slot to a Uint256Slot.
     */
    function asUint256(bytes32 slot) internal pure returns (Uint256Slot) {
        return Uint256Slot.wrap(slot);
    }

    /**
     * @dev UDVT that represents a slot holding a int256.
     */
    type Int256Slot is bytes32;

    /**
     * @dev Cast an arbitrary slot to a Int256Slot.
     */
    function asInt256(bytes32 slot) internal pure returns (Int256Slot) {
        return Int256Slot.wrap(slot);
    }

    /**
     * @dev Load the value held at location `slot` in transient storage.
     */
    function tload(AddressSlot slot) internal view returns (address value) {
        assembly ("memory-safe") {
            value := tload(slot)
        }
    }

    /**
     * @dev Store `value` at location `slot` in transient storage.
     */
    function tstore(AddressSlot slot, address value) internal {
        assembly ("memory-safe") {
            tstore(slot, value)
        }
    }

    /**
     * @dev Load the value held at location `slot` in transient storage.
     */
    function tload(BooleanSlot slot) internal view returns (bool value) {
        assembly ("memory-safe") {
            value := tload(slot)
        }
    }

    /**
     * @dev Store `value` at location `slot` in transient storage.
     */
    function tstore(BooleanSlot slot, bool value) internal {
        assembly ("memory-safe") {
            tstore(slot, value)
        }
    }

    /**
     * @dev Load the value held at location `slot` in transient storage.
     */
    function tload(Bytes32Slot slot) internal view returns (bytes32 value) {
        assembly ("memory-safe") {
            value := tload(slot)
        }
    }

    /**
     * @dev Store `value` at location `slot` in transient storage.
     */
    function tstore(Bytes32Slot slot, bytes32 value) internal {
        assembly ("memory-safe") {
            tstore(slot, value)
        }
    }

    /**
     * @dev Load the value held at location `slot` in transient storage.
     */
    function tload(Uint256Slot slot) internal view returns (uint256 value) {
        assembly ("memory-safe") {
            value := tload(slot)
        }
    }

    /**
     * @dev Store `value` at location `slot` in transient storage.
     */
    function tstore(Uint256Slot slot, uint256 value) internal {
        assembly ("memory-safe") {
            tstore(slot, value)
        }
    }

    /**
     * @dev Load the value held at location `slot` in transient storage.
     */
    function tload(Int256Slot slot) internal view returns (int256 value) {
        assembly ("memory-safe") {
            value := tload(slot)
        }
    }

    /**
     * @dev Store `value` at location `slot` in transient storage.
     */
    function tstore(Int256Slot slot, int256 value) internal {
        assembly ("memory-safe") {
            tstore(slot, value)
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "src/utils/Constants.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

library Constants {
    address public constant NATIVE_CURRENCY = address(0);

    /// @notice The factor used to calculate percentages for fee value.
    /// It is set to 100_000 to handle basis points (0.001% increments)
    uint256 public constant FEE_BASIS_POINTS = 1e5; // ex: 10% = 10_000

    // TODO: Get approve from decision-maker
    uint256 public constant MAX_PROTOCOL_FEE_VALUE = 3e3; // 3_000 = 3%
    uint256 public constant MAX_REFERRAL_FEE_VALUE = 3e4; // 30_000 = 30%

    // TODO: Get approve from decision-maker
    uint256 public constant MAX_REFERRAL_FEES_PER_ORDER = 10;
}
"
    },
    "src/vaults/libraries/ClaimValidator.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

struct ClaimInfo {
    uint256 uuid;
    address recipient;
    address token;
    uint256 amount;
}

library ClaimValidator {
    bytes32 public constant CLAIM_INFO_TYPEHASH =
        keccak256(abi.encodePacked("ClaimInfo(uint256 uuid,address recipient,address token,uint256 amount)"));

    bytes32 public constant OPERATOR_SIGNATURE_TYPEHASH = keccak256(
        abi.encodePacked(
            "OperatorSignature(ClaimInfo claimInfo,uint256 nonce)ClaimInfo(uint256 uuid,address recipient,address token,uint256 amount)"
        )
    );

    /// @notice Thrown when insufficient balance for claim
    error InsufficientBalance(uint256 balance, uint256 claimedAmount);

    /// @notice Thrown when claim amount is zero
    error ZeroAmount();

    /// @notice Thrown when claim recipient is zero
    error ZeroAddress();

    /// @notice Thrown when claim info signature is invalid
    error InvalidClaimSignature();

    function validate(
        ClaimInfo calldata claimInfo,
        bytes calldata signature,
        address operator,
        mapping(uint256 uuid => mapping(address token => uint256 balance)) storage balances,
        mapping(uint256 => uint256) storage nonces
    ) internal view {
        if (claimInfo.amount == 0) {
            revert ZeroAmount();
        }

        if (claimInfo.recipient == address(0)) {
            revert ZeroAddress();
        }

        uint256 balance = balances[claimInfo.uuid][claimInfo.token];
        if (balance < claimInfo.amount) {
            revert InsufficientBalance(balance, claimInfo.amount);
        }

        bool isValid = _isValidSignature(claimInfo, signature, operator, nonces[claimInfo.uuid] + 1);
        if (!isValid) {
            revert InvalidClaimSignature();
        }
    }

    function _isValidSignature(ClaimInfo calldata claimInfo, bytes calldata signature, address operator, uint256 nonce)
        private
        view
        returns (bool)
    {
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _domainSeparator(), _getDigest(claimInfo, nonce)));
        return ECDSA.recover(digest, signature) == operator;
    }

    function _domainSeparator() private view returns (bytes32) {
        uint256 chainId;

        assembly {
            chainId := chainid()
        }

        return keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256("Jupbot-ReferralFeeVault"),
                keccak256("1"),
                chainId,
                address(this)
            )
        );
    }

    function _getDigest(ClaimInfo calldata claimInfo, uint256 nonce) private pure returns (bytes32) {
        bytes32 claimInfoHash = keccak256(
            abi.encode(CLAIM_INFO_TYPEHASH, claimInfo.uuid, claimInfo.recipient, claimInfo.token, claimInfo.amount)
        );

        return keccak256(abi.encode(OPERATOR_SIGNATURE_TYPEHASH, claimInfoHash, nonce));
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * retu

Tags:
ERC20, ERC165, Proxy, Pausable, Upgradeable, Factory|addr:0x4b91257f25341df444ed20d62152fa132a2b2ac8|verified:true|block:23701820|tx:0x0d5c0d8c76a9378b56a20d10dfbbfe73b9e2a8b3683f88b92d03a9ce3b28c1f3|first_check:1761994920

Submitted on: 2025-11-01 12:02:01

Comments

Log in to comment.

No comments yet.