UniversalDepositAccount

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/UniversalDepositAccount.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import {IUniversalDepositManager} from './interfaces/IUniversalDepositManager.sol';
import {Utils} from './utils/Utils.sol';
import {
  MessagingFee,
  MessagingReceipt,
  OFTReceipt,
  SendParam
} from '@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol';
import {OwnableUpgradeable} from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import {Initializable} from '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import {StargateBase} from '@stargatefinance/stg-evm-v2/src/StargateBase.sol';
import {IStargate} from '@stargatefinance/stg-evm-v2/src/interfaces/IStargate.sol';
import {IERC20} from 'forge-std/interfaces/IERC20.sol';

/**
 * @title UniversalDepositAccount
 * @author gnosis
 * @notice User-controlled proxy contract for cross-chain token deposits
 * @dev This contract is deployed as an upgradeable proxy per user per destination chain.
 *      It facilitates automated bridging of deposited tokens to a specified recipient address.
 *      Each account is owned by the user who created it and can bridge tokens to one destination chain.
 */
contract UniversalDepositAccount is Initializable, OwnableUpgradeable {
  /// @notice Contract version for upgrade compatibility
  uint8 public constant VERSION = 1;

  /// @notice Reference to the UniversalDepositManager for routing configurations
  IUniversalDepositManager public udManager;

  /// @notice The destination chain ID where tokens will be bridged
  uint256 public dstChainId;

  /// @notice The address that will receive tokens on the destination chain
  address public recipient;

  /// @notice Incremental counter for bridge operations
  uint256 public nonce;

  /**
   * @notice Emitted when a bridge operation is initiated
   * @param nonce The operation counter at the time of bridging
   */
  event BridgingInitiated(uint256 indexed nonce);

  /**
   * @notice Emitted when unsupported tokens are withdrawn by the owner
   * @param token The token address that was withdrawn
   * @param amount The amount that was withdrawn
   */
  event WithdrawUnsupportedToken(address indexed token, uint256 indexed amount);

  /// @notice Thrown when an invalid address (zero address) is provided
  /// @param givenAddress The invalid address that was provided
  error InvalidAddress(address givenAddress);

  /// @notice Thrown when an operation involves zero or insufficient amount
  /// @param amount The insufficient amount provided
  error AmountNotEnough(uint256 amount);

  /// @notice Thrown when attempting to withdraw a supported token or zero address
  /// @param token The invalid token address
  error InvalidToken(address token);

  /// @notice Thrown when insufficient native tokens are available for bridge fees
  /// @param balance The current native token balance
  /// @param required The required native token amount
  error InsufficientNativeToken(uint256 balance, uint256 required);

  /// @notice Thrown when the slipppage is too high from the minimum expected received amount
  /// @param minAmount minimum amount that should expect to receive
  /// @param actualReceivedAmount actual amount that should receive
  error SlippageToHigh(uint256 minAmount, uint256 actualReceivedAmount);

  /// @notice Thrown when ETH is sent to the contract (not supported)
  error ETHNotSupported();

  /**
   * @notice Constructor that disables initializers to prevent direct implementation usage
   * @dev Required for upgradeable proxy pattern security
   */
  /// @custom:oz-upgrades-unsafe-allow constructor
  constructor() {
    _disableInitializers();
  }

  /**
   * @notice Initializes the proxy instance with user-specific configuration
   * @dev Can only be called once per proxy. Sets up ownership and routing parameters.
   * @param _owner The address that will own this deposit account
   * @param _recipient The address that will receive bridged tokens on the destination chain
   * @param _dstChainId The destination chain ID for token bridging
   * @param _udManager The UniversalDepositManager contract address for routing info
   */
  function initialize(address _owner, address _recipient, uint256 _dstChainId, address _udManager) external initializer {
    if (_owner == address(0)) revert InvalidAddress(_owner);
    if (_recipient == address(0)) revert InvalidAddress(_recipient);

    __Ownable_init();
    transferOwnership(_owner);
    recipient = _recipient;
    dstChainId = _dstChainId;

    udManager = IUniversalDepositManager(_udManager);
  }

  /**
   * @notice Calculates the required native token amount and parameters for Stargate bridging
   * @dev Queries Stargate for accurate fee estimation and minimum amount calculations
   * @param amount The amount of tokens to bridge
   * @param srcStargateToken The Stargate pool/OFT address for the source token
   * @param maxSlippage max slippage allowed, 10000 = 100%
   * @return valueToSend The total native token amount needed
   * @return sendParam The complete SendParam struct for the Stargate transaction
   * @return messagingFee The LayerZero messaging fee breakdown
   */
  function quoteStargateFee(
    uint256 amount,
    address srcStargateToken,
    uint256 maxSlippage
  ) public view returns (uint256 valueToSend, SendParam memory sendParam, MessagingFee memory messagingFee) {
    if (amount == 0) revert AmountNotEnough(amount);

    sendParam = SendParam({
      dstEid: udManager.chainIdToEidMap(dstChainId),
      to: Utils._addressToBytes32(recipient),
      amountLD: amount,
      minAmountLD: amount, // Will be updated with quote
      extraOptions: new bytes(0), // Default, can be customized
      composeMsg: new bytes(0), // Default, can be customized
      oftCmd: '' // Empty for taxi mode
    });

    // Get accurate minimum amount from quote
    (,, OFTReceipt memory receipt) = IStargate(srcStargateToken).quoteOFT(sendParam);
    uint256 minReceivedAmount = (receipt.amountReceivedLD * (10_000 - maxSlippage)) / 10_000;
    if (receipt.amountReceivedLD < minReceivedAmount) {
      revert SlippageToHigh(minReceivedAmount, receipt.amountReceivedLD);
    }
    sendParam.minAmountLD = receipt.amountReceivedLD;

    // Get messaging fee
    messagingFee = IStargate(srcStargateToken).quoteSend(sendParam, false); // false for not paying with ZRO
    valueToSend = messagingFee.nativeFee;

    // If sending native gas token, add amount to valueToSend
    if (IStargate(srcStargateToken).token() == address(0x0)) {
      valueToSend += sendParam.amountLD;
    }
  }

  /**
   * @notice Rejects direct ETH transfers to the contract
   * @dev ETH bridging is not supported in this implementation
   */
  receive() external payable {
    revert ETHNotSupported();
  }

  /**
   * @notice Emergency withdrawal function for unsupported tokens
   * @dev Only callable by the account owner. Cannot withdraw supported bridging tokens.
   * @param token The token address to withdraw (must not be supported for bridging)
   * @param amount The amount to withdraw (must be greater than 0)
   */
  function withdrawToken(address token, uint256 amount) external onlyOwner {
    if (udManager.isSrcTokenSupported(token) || token == address(0)) revert InvalidToken(token);
    if (amount == 0) revert AmountNotEnough(amount);
    IERC20(token).transfer(owner(), amount);

    emit WithdrawUnsupportedToken(token, amount);
  }

  /**
   * @notice Initiates cross-chain bridging of the entire token balance via Stargate
   * @dev Can be called by anyone who provides sufficient native tokens for fees.
   *      Automatically bridges the full balance of the specified token to the configured recipient.
   * @param srcToken The source token address to bridge
   * @param maxSlippage max slippage allowed, 10000 = 100%
   * @return msgReceipt LayerZero messaging receipt with transaction details
   * @return oftReceipt OFT receipt with amount and fee information
   */
  function settle(
    address srcToken,
    uint256 maxSlippage
  ) public payable returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {
    IUniversalDepositManager.StargateTokenRoute memory stargateTokenRoute =
      udManager.getStargateRoute(srcToken, dstChainId);

    uint256 bridgeAmount = IERC20(srcToken).balanceOf(address(this));

    // Calculate maximum bridgeable amount based on Stargate pool liquidity
    {
      uint256 conversionRate = Utils._getStargateConversionRate(IERC20(srcToken).decimals());
      uint64 bridgeAmountSD = Utils._ld2sd(bridgeAmount, conversionRate);
      uint64 credit = StargateBase(stargateTokenRoute.srcStargateToken).paths(stargateTokenRoute.dstEid);
      uint64 maxAmountSD = bridgeAmountSD > credit ? credit : bridgeAmountSD;
      bridgeAmount = Utils._sd2ld(maxAmountSD, conversionRate);
    }

    IERC20(srcToken).approve(stargateTokenRoute.srcStargateToken, bridgeAmount);
    (uint256 valueToSend, SendParam memory sendParam, MessagingFee memory messagingFee) =
      quoteStargateFee(bridgeAmount, stargateTokenRoute.srcStargateToken, maxSlippage);
    if (address(this).balance < valueToSend) revert InsufficientNativeToken(address(this).balance, valueToSend);

    (msgReceipt, oftReceipt,) =
      IStargate(stargateTokenRoute.srcStargateToken).sendToken{value: valueToSend}(sendParam, messagingFee, owner()); // Use owner as refundAddress
    nonce += 1;
    emit BridgingInitiated(nonce);
  }
}
"
    },
    "src/interfaces/IUniversalDepositManager.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

interface IUniversalDepositManager {
  struct StargateTokenRoute {
    address srcStargateToken;
    address dstStargateToken;
    uint32 srcEid;
    uint32 dstEid;
    TokenRoute tokenRoute;
  }

  struct TokenRoute {
    address srcToken;
    address dstToken;
    uint256 srcChainId;
    uint256 dstChainId;
  }

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  function chainIdToEidMap(
    uint256
  ) external view returns (uint32);
  function getSGRouteKey(
    StargateTokenRoute memory stargateTokenRoute
  ) external pure returns (bytes32);
  function getStargateRoute(
    address srcToken,
    uint256 dstChainId
  ) external view returns (StargateTokenRoute memory stargateTokenRoute);
  function isSGRouteSupported(
    bytes32
  ) external view returns (bool);
  function isSrcTokenSupported(
    address
  ) external view returns (bool);
  function owner() external view returns (address);
  function renounceOwnership() external;
  function setChainIdEid(uint256 chainId, uint32 eid) external;
  function setStargateRoute(
    StargateTokenRoute memory stargateTokenRoute
  ) external;
  function stargateTokenRouteMap(
    address srcToken,
    uint256 dstChainId
  )
    external
    view
    returns (
      address srcStargateToken,
      address dstStargateToken,
      uint32 srcEid,
      uint32 dstEid,
      TokenRoute memory tokenRoute
    );
  function transferOwnership(
    address newOwner
  ) external;
}
"
    },
    "src/utils/Utils.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

library Utils {
  function _getStargateConversionRate(
    uint8 _tokenDecimals
  ) internal pure returns (uint256) {
    return 10 ** (_tokenDecimals - 6);
  }

  function _sd2ld(uint64 _amountSD, uint256 _conversionRate) internal pure returns (uint256) {
    return uint256(_amountSD) * _conversionRate;
  }

  function _ld2sd(uint256 _amountLD, uint256 _conversionRate) internal pure returns (uint64) {
    return uint64(_amountLD / _conversionRate);
  }

  function _addressToBytes32(
    address _addr
  ) internal pure returns (bytes32) {
    return bytes32(uint256(uint160(_addr)));
  }
}
"
    },
    "node_modules/@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { MessagingReceipt, MessagingFee } from "../../oapp/OAppSender.sol";

/**
 * @dev Struct representing token parameters for the OFT send() operation.
 */
struct SendParam {
    uint32 dstEid; // Destination endpoint ID.
    bytes32 to; // Recipient address.
    uint256 amountLD; // Amount to send in local decimals.
    uint256 minAmountLD; // Minimum amount to send in local decimals.
    bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
    bytes composeMsg; // The composed message for the send() operation.
    bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.
}

/**
 * @dev Struct representing OFT limit information.
 * @dev These amounts can change dynamically and are up the the specific oft implementation.
 */
struct OFTLimit {
    uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.
    uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.
}

/**
 * @dev Struct representing OFT receipt information.
 */
struct OFTReceipt {
    uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.
    // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.
    uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.
}

/**
 * @dev Struct representing OFT fee details.
 * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.
 */
struct OFTFeeDetail {
    int256 feeAmountLD; // Amount of the fee in local decimals.
    string description; // Description of the fee.
}

/**
 * @title IOFT
 * @dev Interface for the OftChain (OFT) token.
 * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.
 * @dev This specific interface ID is '0x02e49c2c'.
 */
interface IOFT {
    // Custom error messages
    error InvalidLocalDecimals();
    error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);

    // Events
    event OFTSent(
        bytes32 indexed guid, // GUID of the OFT message.
        uint32 dstEid, // Destination Endpoint ID.
        address indexed fromAddress, // Address of the sender on the src chain.
        uint256 amountSentLD, // Amount of tokens sent in local decimals.
        uint256 amountReceivedLD // Amount of tokens received in local decimals.
    );
    event OFTReceived(
        bytes32 indexed guid, // GUID of the OFT message.
        uint32 srcEid, // Source Endpoint ID.
        address indexed toAddress, // Address of the recipient on the dst chain.
        uint256 amountReceivedLD // Amount of tokens received in local decimals.
    );

    /**
     * @notice Retrieves interfaceID and the version of the OFT.
     * @return interfaceId The interface ID.
     * @return version The version.
     *
     * @dev interfaceId: This specific interface ID is '0x02e49c2c'.
     * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.
     * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.
     * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)
     */
    function oftVersion() external view returns (bytes4 interfaceId, uint64 version);

    /**
     * @notice Retrieves the address of the token associated with the OFT.
     * @return token The address of the ERC20 token implementation.
     */
    function token() external view returns (address);

    /**
     * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
     * @return requiresApproval Needs approval of the underlying token implementation.
     *
     * @dev Allows things like wallet implementers to determine integration requirements,
     * without understanding the underlying token implementation.
     */
    function approvalRequired() external view returns (bool);

    /**
     * @notice Retrieves the shared decimals of the OFT.
     * @return sharedDecimals The shared decimals of the OFT.
     */
    function sharedDecimals() external view returns (uint8);

    /**
     * @notice Provides a quote for OFT-related operations.
     * @param _sendParam The parameters for the send operation.
     * @return limit The OFT limit information.
     * @return oftFeeDetails The details of OFT fees.
     * @return receipt The OFT receipt information.
     */
    function quoteOFT(
        SendParam calldata _sendParam
    ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);

    /**
     * @notice Provides a quote for the send() operation.
     * @param _sendParam The parameters for the send() operation.
     * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
     * @return fee The calculated LayerZero messaging fee from the send() operation.
     *
     * @dev MessagingFee: LayerZero msg fee
     *  - nativeFee: The native fee.
     *  - lzTokenFee: The lzToken fee.
     */
    function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);

    /**
     * @notice Executes the send() operation.
     * @param _sendParam The parameters for the send operation.
     * @param _fee The fee information supplied by the caller.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess funds from fees etc. on the src.
     * @return receipt The LayerZero messaging receipt from the send() operation.
     * @return oftReceipt The OFT receipt information.
     *
     * @dev MessagingReceipt: LayerZero msg receipt
     *  - guid: The unique identifier for the sent message.
     *  - nonce: The nonce of the sent message.
     *  - fee: The LayerZero fee incurred for the message.
     */
    function send(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable returns (MessagingReceipt memory, OFTReceipt memory);
}
"
    },
    "node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function __Ownable_init() internal onlyInitializing {
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}
"
    },
    "node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}
"
    },
    "node_modules/@stargatefinance/stg-evm-v2/src/StargateBase.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.22;

import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import { ILayerZeroEndpointV2 } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppCore.sol";
import { Origin } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
// Solidity does not support splitting import across multiple lines
// solhint-disable-next-line max-line-length
import { OFTLimit, OFTFeeDetail, OFTReceipt, SendParam, MessagingReceipt, MessagingFee, IOFT } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol";
import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol";

import { IStargate, Ticket } from "./interfaces/IStargate.sol";
import { IStargateFeeLib, FeeParams } from "./interfaces/IStargateFeeLib.sol";
import { ITokenMessaging, RideBusParams, TaxiParams } from "./interfaces/ITokenMessaging.sol";
import { ITokenMessagingHandler } from "./interfaces/ITokenMessagingHandler.sol";
import { ICreditMessagingHandler, Credit, TargetCredit } from "./interfaces/ICreditMessagingHandler.sol";
import { Path } from "./libs/Path.sol";
import { Transfer } from "./libs/Transfer.sol";

/// @title The base contract for StargateOFT, StargatePool, StargatePoolMigratable, and StargatePoolNative.
abstract contract StargateBase is Transfer, IStargate, ITokenMessagingHandler, ICreditMessagingHandler {
    using SafeCast for uint256;

    // Stargate status
    uint8 internal constant NOT_ENTERED = 1;
    uint8 internal constant ENTERED = 2;
    uint8 internal constant PAUSED = 3;

    /// @dev The token for the Pool or OFT.
    /// @dev address(0) indicates native coin, such as ETH.
    address public immutable override token;
    /// @dev The shared decimals (lowest common decimals between chains).
    uint8 public immutable override sharedDecimals;
    /// @dev The rate between local decimals and shared decimals.
    uint256 internal immutable convertRate;

    /// @dev The local LayerZero EndpointV2.
    ILayerZeroEndpointV2 public immutable endpoint;
    /// @dev The local LayerZero endpoint ID
    uint32 public immutable localEid;

    address internal feeLib;
    /// @dev The StargateBase status.  Options include 1. NOT_ENTERED 2. ENTERED and 3. PAUSED.
    uint8 public status = NOT_ENTERED;
    /// @dev The treasury accrued fees, stored in SD.
    uint64 public treasuryFee;

    address internal creditMessaging;
    address internal lzToken;
    address internal planner;
    address internal tokenMessaging;
    address internal treasurer;

    /// @dev Mapping of paths from this chain to other chains identified by their endpoint ID.
    mapping(uint32 eid => Path path) public paths;

    /// @dev A store for tokens that could not be delivered because _outflow() failed.
    /// @dev retryReceiveToken() can be called to retry the receive.
    mapping(bytes32 guid => mapping(uint8 index => bytes32 hash)) public unreceivedTokens;

    modifier onlyCaller(address _caller) {
        if (msg.sender != _caller) revert Stargate_Unauthorized();
        _;
    }

    modifier nonReentrantAndNotPaused() {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        if (status != NOT_ENTERED) {
            if (status == ENTERED) revert Stargate_ReentrantCall();
            revert Stargate_Paused();
        }
        // Any calls to nonReentrant after this point will fail
        status = ENTERED;
        _;
        status = NOT_ENTERED;
    }

    error Stargate_ReentrantCall();
    error Stargate_InvalidTokenDecimals();
    error Stargate_Unauthorized();
    error Stargate_SlippageTooHigh();
    error Stargate_UnreceivedTokenNotFound();
    error Stargate_OutflowFailed();
    error Stargate_InvalidAmount();
    error Stargate_InsufficientFare();
    error Stargate_InvalidPath();
    error Stargate_LzTokenUnavailable();
    error Stargate_Paused();
    error Stargate_RecoverTokenUnsupported();

    event AddressConfigSet(AddressConfig config);
    event CreditsSent(uint32 dstEid, Credit[] credits);
    event CreditsReceived(uint32 srcEid, Credit[] credits);
    event UnreceivedTokenCached(
        bytes32 guid,
        uint8 index,
        uint32 srcEid,
        address receiver,
        uint256 amountLD,
        bytes composeMsg
    );
    event OFTPathSet(uint32 dstEid, bool oft);
    event PauseSet(bool paused);
    event PlannerFeeWithdrawn(uint256 amount);
    event TreasuryFeeAdded(uint64 amountSD);
    event TreasuryFeeWithdrawn(address to, uint64 amountSD);

    struct AddressConfig {
        address feeLib;
        address planner;
        address treasurer;
        address tokenMessaging;
        address creditMessaging;
        address lzToken;
    }

    /// @notice Create a new Stargate contract
    /// @dev Reverts with InvalidTokenDecimals if the token decimals are smaller than the shared decimals.
    /// @param _token The token for the pool or oft. If the token is address(0), it is the native coin
    /// @param _tokenDecimals The number of decimals for this tokens implementation on this chain
    /// @param _sharedDecimals The number of decimals shared between all implementations of the OFT
    /// @param _endpoint The LZ endpoint contract
    /// @param _owner The owner of this contract
    constructor(address _token, uint8 _tokenDecimals, uint8 _sharedDecimals, address _endpoint, address _owner) {
        token = _token;
        if (_tokenDecimals < _sharedDecimals) revert Stargate_InvalidTokenDecimals();
        convertRate = 10 ** (_tokenDecimals - _sharedDecimals);
        sharedDecimals = _sharedDecimals;

        endpoint = ILayerZeroEndpointV2(_endpoint);
        localEid = endpoint.eid();
        _transferOwnership(_owner);
    }

    // ---------------------------------- Only Owner ------------------------------------------

    /// @notice Configure the roles for this contract.
    /// @param _config An AddressConfig object containing the addresses for the different roles used by Stargate.
    function setAddressConfig(AddressConfig calldata _config) external onlyOwner {
        feeLib = _config.feeLib;
        planner = _config.planner;
        treasurer = _config.treasurer;
        tokenMessaging = _config.tokenMessaging;
        creditMessaging = _config.creditMessaging;
        lzToken = _config.lzToken;
        emit AddressConfigSet(_config);
    }

    /// @notice Sets a given Path as using OFT or resets it from OFT.
    /// @dev Set the path as OFT if the remote chain is using OFT.
    /// @dev When migrating from OFT to pool on remote chain (e.g. migrate USDC to circles), reset the path to non-OFT.
    /// @dev Reverts with InvalidPath if the destination chain is the same as local.
    /// @param _dstEid The destination chain endpoint ID
    /// @param _oft Whether to set or reset the path
    function setOFTPath(uint32 _dstEid, bool _oft) external onlyOwner {
        if (_dstEid == localEid) revert Stargate_InvalidPath();
        paths[_dstEid].setOFTPath(_oft);
        emit OFTPathSet(_dstEid, _oft);
    }

    // ---------------------------------- Only Treasurer ------------------------------------------

    /// @notice Withdraw from the accrued fees in the treasury.
    /// @param _to The destination account
    /// @param _amountSD The amount to withdraw in SD
    function withdrawTreasuryFee(address _to, uint64 _amountSD) external onlyCaller(treasurer) {
        treasuryFee -= _amountSD;
        _safeOutflow(_to, _sd2ld(_amountSD));
        emit TreasuryFeeWithdrawn(_to, _amountSD);
    }

    /// @notice Add tokens to the treasury, from the senders account.
    /// @dev Only used for increasing the overall budget for transaction rewards
    /// @dev The treasuryFee is essentially the reward pool.
    /// @dev Rewards are capped to the treasury amount, which limits exposure so
    /// @dev Stargate does not pay beyond what it's charged.
    /// @param _amountLD The amount to add in LD
    function addTreasuryFee(uint256 _amountLD) external payable onlyCaller(treasurer) {
        _assertMsgValue(_amountLD);
        uint64 amountSD = _inflow(msg.sender, _amountLD);
        treasuryFee += amountSD;
        emit TreasuryFeeAdded(amountSD);
    }

    /// @dev Recover tokens sent to this contract by mistake.
    /// @dev Only the treasurer can recover the token.
    /// @dev Reverts with Stargate_RecoverTokenUnsupported if the treasurer attempts to withdraw StargateBase.token().
    /// @param _token the token to recover. if 0x0 then it is native token
    /// @param _to the address to send the token to
    /// @param _amount the amount to send
    function recoverToken(
        address _token,
        address _to,
        uint256 _amount
    ) public virtual nonReentrantAndNotPaused onlyCaller(treasurer) returns (uint256) {
        /// @dev Excess native is considered planner accumulated fees.
        if (_token == address(0)) revert Stargate_RecoverTokenUnsupported();
        Transfer.safeTransfer(_token, _to, _amount, false);
        return _amount;
    }

    // ---------------------------------- Only Planner ------------------------------------------

    /// @notice Pause or unpause a Stargate
    /// @dev Be careful with this call, as it unsets the re-entry guard.
    /// @param _paused Whether to pause or unpause the stargate
    function setPause(bool _paused) external onlyCaller(planner) {
        if (status == ENTERED) revert Stargate_ReentrantCall();
        status = _paused ? PAUSED : NOT_ENTERED;
        emit PauseSet(_paused);
    }

    function _plannerFee() internal view virtual returns (uint256) {
        return address(this).balance;
    }

    function plannerFee() external view returns (uint256 available) {
        available = _plannerFee();
    }

    /// @notice Withdraw planner fees accumulated in StargateBase.
    /// @dev The planner fee is accumulated in StargateBase to avoid the cost of passing msg.value to TokenMessaging.
    function withdrawPlannerFee() external virtual onlyCaller(planner) {
        uint256 available = _plannerFee();
        Transfer.safeTransferNative(msg.sender, available, false);
        emit PlannerFeeWithdrawn(available);
    }

    // ------------------------------- Public Functions ---------------------------------------

    /// @notice Send tokens through the Stargate
    /// @dev Emits OFTSent when the send is successful
    /// @param _sendParam The SendParam object detailing the transaction
    /// @param _fee The MessagingFee object describing the fee to pay
    /// @param _refundAddress The address to refund any LZ fees paid in excess
    /// @return msgReceipt The receipt proving the message was sent
    /// @return oftReceipt The receipt proving the OFT swap
    function send(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable override returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {
        (msgReceipt, oftReceipt, ) = sendToken(_sendParam, _fee, _refundAddress);
    }

    function sendToken(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    )
        public
        payable
        override
        nonReentrantAndNotPaused
        returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt, Ticket memory ticket)
    {
        // step 1: assets inflows and apply the fee to the input amount
        (bool isTaxi, uint64 amountInSD, uint64 amountOutSD) = _inflowAndCharge(_sendParam);

        // step 2: generate the oft receipt
        oftReceipt = OFTReceipt(_sd2ld(amountInSD), _sd2ld(amountOutSD));

        // step 3: assert the messaging fee
        MessagingFee memory messagingFee = _assertMessagingFee(_fee, oftReceipt.amountSentLD);

        // step 4: send the token depending on the mode Taxi or Bus
        if (isTaxi) {
            msgReceipt = _taxi(_sendParam, messagingFee, amountOutSD, _refundAddress);
        } else {
            (msgReceipt, ticket) = _rideBus(_sendParam, messagingFee, amountOutSD, _refundAddress);
        }

        emit OFTSent(
            msgReceipt.guid,
            _sendParam.dstEid,
            msg.sender,
            oftReceipt.amountSentLD,
            oftReceipt.amountReceivedLD
        );
    }

    /// @notice Retry receiving a token that initially failed.
    /// @dev The message has been delivered by the Messaging layer, so it is ok for anyone to retry.
    /// @dev try to receive the token if the previous attempt failed in lzReceive
    /// @dev Reverts with UnreceivedTokenNotFound if the message is not found in the cache
    /// @dev Emits OFTReceived if the receive succeeds
    /// @param _guid The global unique ID for the message that failed
    /// @param _index The index of the message that failed
    /// @param _srcEid The source endpoint ID for the message that failed
    /// @param _receiver The account receiver for the message that failed
    /// @param _amountLD The amount of tokens in LD to transfer to the account
    /// @param _composeMsg The bytes representing the compose message in the message that failed
    function retryReceiveToken(
        bytes32 _guid,
        uint8 _index,
        uint32 _srcEid,
        address _receiver,
        uint256 _amountLD,
        bytes calldata _composeMsg
    ) external nonReentrantAndNotPaused {
        if (unreceivedTokens[_guid][_index] != keccak256(abi.encodePacked(_srcEid, _receiver, _amountLD, _composeMsg)))
            revert Stargate_UnreceivedTokenNotFound();
        delete unreceivedTokens[_guid][_index];

        _safeOutflow(_receiver, _amountLD);
        _postOutflow(_ld2sd(_amountLD));
        if (_composeMsg.length > 0) {
            endpoint.sendCompose(_receiver, _guid, 0, _composeMsg);
        }
        emit OFTReceived(_guid, _srcEid, _receiver, _amountLD);
    }

    // ------------------------------- Only Messaging ---------------------------------------

    /// @notice Entrypoint for receiving tokens
    /// @dev Emits OFTReceived when the OFT token is correctly received
    /// @dev Emits UnreceivedTokenCached when the OFT token is not received
    /// @param _origin The Origin struct describing the origin, useful for composing
    /// @param _guid The global unique ID for this message, useful for composing
    function receiveTokenBus(
        Origin calldata _origin,
        bytes32 _guid,
        uint8 _seatNumber,
        address _receiver,
        uint64 _amountSD
    ) external nonReentrantAndNotPaused onlyCaller(tokenMessaging) {
        uint256 amountLD = _sd2ld(_amountSD);

        bool success = _outflow(_receiver, amountLD);
        if (success) {
            _postOutflow(_amountSD);
            emit OFTReceived(_guid, _origin.srcEid, _receiver, amountLD);
        } else {
            /**
             * @dev The busRide mode does not support composeMsg in any form. Thus we hardcode it to ""
             */
            unreceivedTokens[_guid][_seatNumber] = keccak256(abi.encodePacked(_origin.srcEid, _receiver, amountLD, ""));
            emit UnreceivedTokenCached(_guid, _seatNumber, _origin.srcEid, _receiver, amountLD, "");
        }
    }

    // taxi mode
    function receiveTokenTaxi(
        Origin calldata _origin,
        bytes32 _guid,
        address _receiver,
        uint64 _amountSD,
        bytes calldata _composeMsg
    ) external nonReentrantAndNotPaused onlyCaller(tokenMessaging) {
        uint256 amountLD = _sd2ld(_amountSD);
        bool hasCompose = _composeMsg.length > 0;
        bytes memory composeMsg;
        if (hasCompose) {
            composeMsg = OFTComposeMsgCodec.encode(_origin.nonce, _origin.srcEid, amountLD, _composeMsg);
        }

        bool success = _outflow(_receiver, amountLD);
        if (success) {
            _postOutflow(_amountSD);
            // send the composeMsg to the endpoint
            if (hasCompose) {
                endpoint.sendCompose(_receiver, _guid, 0, composeMsg);
            }
            emit OFTReceived(_guid, _origin.srcEid, _receiver, amountLD);
        } else {
            /**
             * @dev We use the '0' index to represent the seat number. This is because for a type 'taxi' msg,
             *      there is only ever one corresponding receiveTokenTaxi function per GUID.
             */
            unreceivedTokens[_guid][0] = keccak256(abi.encodePacked(_origin.srcEid, _receiver, amountLD, composeMsg));
            emit UnreceivedTokenCached(_guid, 0, _origin.srcEid, _receiver, amountLD, composeMsg);
        }
    }

    function sendCredits(
        uint32 _dstEid,
        TargetCredit[] calldata _credits
    ) external nonReentrantAndNotPaused onlyCaller(creditMessaging) returns (Credit[] memory) {
        Credit[] memory credits = new Credit[](_credits.length);
        uint256 index = 0;
        for (uint256 i = 0; i < _credits.length; i++) {
            TargetCredit calldata c = _credits[i];
            uint64 decreased = paths[c.srcEid].tryDecreaseCredit(c.amount, c.minAmount);
            if (decreased > 0) credits[index++] = Credit(c.srcEid, decreased);
        }
        // resize the array to the actual number of credits
        assembly {
            mstore(credits, index)
        }
        emit CreditsSent(_dstEid, credits);
        return credits;
    }

    /// @notice Entrypoint for receiving credits into paths
    /// @dev Emits CreditsReceived when credits are received
    /// @param _srcEid The endpoint ID of the source of credits
    /// @param _credits An array indicating to which paths and how much credits to add
    function receiveCredits(
        uint32 _srcEid,
        Credit[] calldata _credits
    ) external nonReentrantAndNotPaused onlyCaller(creditMessaging) {
        for (uint256 i = 0; i < _credits.length; i++) {
            Credit calldata c = _credits[i];
            paths[c.srcEid].increaseCredit(c.amount);
        }
        emit CreditsReceived(_srcEid, _credits);
    }

    // ---------------------------------- View Functions ------------------------------------------

    /// @notice Provides a quote for sending OFT to another chain.
    /// @dev Implements the IOFT interface
    /// @param _sendParam The parameters for the send operation
    /// @return limit The information on OFT transfer limits
    /// @return oftFeeDetails The details of OFT transaction cost or reward
    /// @return receipt The OFT receipt information, indicating how many tokens would be sent and received
    function quoteOFT(
        SendParam calldata _sendParam
    ) external view returns (OFTLimit memory limit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory receipt) {
        // cap the transfer to the paths limit
        limit = OFTLimit(_sd2ld(1), _sd2ld(paths[_sendParam.dstEid].credit));

        // get the expected amount in the destination chain from FeeLib
        uint64 amountInSD = _ld2sd(_sendParam.amountLD > limit.maxAmountLD ? limit.maxAmountLD : _sendParam.amountLD);
        FeeParams memory params = _buildFeeParams(_sendParam.dstEid, amountInSD, _isTaxiMode(_sendParam.oftCmd));
        uint64 amountOutSD = IStargateFeeLib(feeLib).applyFeeView(params);

        // fill in the FeeDetails if there is a fee or reward
        if (amountOutSD != amountInSD) {
            oftFeeDetails = new OFTFeeDetail[](1);
            if (amountOutSD < amountInSD) {
                // fee
                oftFeeDetails[0] = OFTFeeDetail(-1 * _sd2ld(amountInSD - amountOutSD).toInt256(), "protocol fee");
            } else if (amountOutSD > amountInSD) {
                // reward
                uint64 reward = amountOutSD - amountInSD;
                (amountOutSD, reward) = _capReward(amountOutSD, reward);
                if (amountOutSD == amountInSD) {
                    // hide the Fee detail if the reward is capped to 0
                    oftFeeDetails = new OFTFeeDetail[](0);
                } else {
                    oftFeeDetails[0] = OFTFeeDetail(_sd2ld(reward).toInt256(), "reward");
                }
            }
        }

        receipt = OFTReceipt(_sd2ld(amountInSD), _sd2ld(amountOutSD));
    }

    /// @notice Provides a quote for the send() operation.
    /// @dev Implements the IOFT interface.
    /// @dev Reverts with InvalidAmount if send mode is drive but value is specified.
    /// @param _sendParam The parameters for the send() operation
    /// @param _payInLzToken Flag indicating whether the caller is paying in the LZ token
    /// @return fee The calculated LayerZero messaging fee from the send() operation
    /// @dev MessagingFee: LayerZero message fee
    ///   - nativeFee: The native fee.
    ///   - lzTokenFee: The LZ token fee.
    function quoteSend(
        SendParam calldata _sendParam,
        bool _payInLzToken
    ) external view returns (MessagingFee memory fee) {
        uint64 amountSD = _ld2sd(_sendParam.amountLD);
        if (amountSD == 0) revert Stargate_InvalidAmount();

        bool isTaxi = _isTaxiMode(_sendParam.oftCmd);
        if (isTaxi) {
            fee = ITokenMessaging(tokenMessaging).quoteTaxi(
                TaxiParams({
                    sender: msg.sender,
                    dstEid: _sendParam.dstEid,
                    receiver: _sendParam.to,
                    amountSD: amountSD,
                    composeMsg: _sendParam.composeMsg,
                    extraOptions: _sendParam.extraOptions
                }),
                _payInLzToken
            );
        } else {
            bool nativeDrop = _sendParam.extraOptions.length > 0;
            fee = ITokenMessaging(tokenMessaging).quoteRideBus(_sendParam.dstEid, nativeDrop);
        }
    }

    /// @notice Returns the current roles configured.
    /// @return An AddressConfig struct containing the current configuration
    function getAddressConfig() external view returns (AddressConfig memory) {
        return
            AddressConfig({
                feeLib: feeLib,
                planner: planner,
                treasurer: treasurer,
                tokenMessaging: tokenMessaging,
                creditMessaging: creditMessaging,
                lzToken: lzToken
            });
    }

    /// @notice Get the OFT version information
    /// @dev Implements the IOFT interface.
    /// @dev 0 version means the message encoding is not compatible with the default OFT.
    /// @return interfaceId The ERC165 interface ID for this contract
    /// @return version The cross-chain compatible message encoding version.
    function oftVersion() external pure override returns (bytes4 interfaceId, uint64 version) {
        return (type(IOFT).interfaceId, 0);
    }

    /// @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
    /// @dev Implements the IOFT interface.
    /// @return Whether approval of the underlying token implementation is required
    function approvalRequired() external pure override returns (bool) {
        return true;
    }

    // ---------------------------------- Internal Functions ------------------------------------------

    /// @notice Ingest value into the contract and charge the Stargate fee.
    /// @dev This is triggered when value is transferred from an account into Stargate to execute a swap.
    /// @param _sendParam A SendParam struct containing the swap information
    function _inflowAndCharge(
        SendParam calldata _sendParam
    ) internal returns (bool isTaxi, uint64 amountInSD, uint64 amountOutSD) {
        isTaxi = _isTaxiMode(_sendParam.oftCmd);
        amountInSD = _inflow(msg.sender, _sendParam.amountLD);

        FeeParams memory feeParams = _buildFeeParams(_sendParam.dstEid, amountInSD, isTaxi);

        amountOutSD = _chargeFee(feeParams, _ld2sd(_sendParam.minAmountLD));

        paths[_sendParam.dstEid].decreaseCredit(amountOutSD); // remove the credit from the path
        _postInflow(amountOutSD); // post inflow actions with the amount deducted by the fee
    }

    /// @notice Consult the FeeLib the fee/reward for sending this token
    /// @dev Reverts with SlippageTooHigh when the slippage amount sent would be below the desired minimum or zero.
    /// @return amountOutSD The actual amount that would be sent after applying fees/rewards
    function _chargeFee(FeeParams memory _feeParams, uint64 _minAmountOutSD) internal returns (uint64 amountOutSD) {
        // get the output amount from the fee library
        amountOutSD = IStargateFeeLib(feeLib).applyFee(_feeParams);

        uint64 amountInSD = _feeParams.amountInSD;
        if (amountOutSD < amountInSD) {
            // fee
            treasuryFee += amountInSD - amountOutSD;
        } else if (amountOutSD > amountInSD) {
            // reward
            uint64 reward = amountOutSD - amountInSD;
            (amountOutSD, reward) = _capReward(amountOutSD, reward);
            if (reward > 0) treasuryFee -= reward;
        }

        if (amountOutSD < _minAmountOutSD || amountOutSD == 0) revert Stargate_SlippageTooHigh(); // 0 not allowed
    }

    function _taxi(
        SendParam calldata _sendParam,
        MessagingFee memory _messagingFee,
        uint64 _amountSD,
        address _refundAddress
    ) internal returns (MessagingReceipt memory receipt) {
        if (_messagingFee.lzTokenFee > 0) _payLzToken(_messagingFee.lzTokenFee); // handle lz token fee

        receipt = ITokenMessaging(tokenMessaging).taxi{ value: _messagingFee.nativeFee }(
            TaxiParams({
                sender: msg.sender,
                dstEid: _sendParam.dstEid,
                receiver: _sendParam.to,
                amountSD: _amountSD,
                composeMsg: _sendParam.composeMsg,
                extraOptions: _sendParam.extraOptions
            }),
            _messagingFee,
            _refundAddress
        );
    }

    function _rideBus(
        SendParam calldata _sendParam,
        MessagingFee memory _messagingFee,
        uint64 _amountSD,
        address _refundAddress
    ) internal virtual returns (MessagingReceipt memory receipt, Ticket memory ticket) {
        if (_messagingFee.lzTokenFee > 0) revert Stargate_LzTokenUnavailable();

        (receipt, ticket) = ITokenMessaging(tokenMessaging).rideBus(
            RideBusParams({
                sender: msg.sender,
                dstEid: _sendParam.dstEid,
                receiver: _sendParam.to,
                amountSD: _amountSD,
                nativeDrop: _sendParam.extraOptions.length > 0
            })
        );

        uint256 busFare = receipt.fee.nativeFee;
        uint256 providedFare = _messagingFee.nativeFee;

        // assert sufficient nativeFee was provided to cover the fare
        if (busFare == providedFare) {
            // return; Do nothing in this case
        } else if (providedFare > busFare) {
            uint256 refund;
            unchecked {
                refund = providedFare - busFare;
            }
            Transfer.transferNative(_refundAddress, refund, false); // no gas limit to refund
        } else {
            revert Stargate_InsufficientFare();
        }
    }

    /// @notice Pay the LZ fee in LZ tokens.
    /// @dev Reverts with LzTokenUnavailable if the LZ token OFT has not been set.
    /// @param _lzTokenFee The fee to pay in LZ tokens
    function _payLzToken(uint256 _lzTokenFee) internal {
        address lzTkn = lzToken;
        if (lzTkn == address(0)) revert Stargate_LzTokenUnavailable();
        Transfer.safeTransferTokenFrom(lzTkn, msg.sender, address(endpoint), _lzTokenFee);
    }

    /// @notice Translate an amount in SD to LD
    /// @dev Since SD <= LD by definition, convertRate >= 1, so there is no rounding errors in this function.
    /// @param _amountSD The amount in SD
    /// @return amountLD The same value expressed in LD
    function _sd2ld(uint64 _amountSD) internal view returns (uint256 amountLD) {
        unchecked {
            amountLD = _amountSD * convertRate;
        }
    }

    /// @notice Translate an value in LD to SD
    /// @dev Since SD <= LD by definition, convertRate >= 1, so there might be rounding during the cast.
    /// @param _amountLD The value in LD
    /// @return amountSD The same value expressed in SD
    function _ld2sd(uint256 _amountLD) internal view returns (uint64 amountSD) {
        unchecked {
            amountSD = SafeCast.toUint64(_amountLD / convertRate);
        }
    }

    /// @dev if _cmd is empty, Taxi mode. Otherwise, Bus mode
    function _isTaxiMode(bytes calldata _oftCmd) internal pure returns (bool) {
        return _oftCmd.length == 0;
    }

    // ---------------------------------- Virtual Functions ------------------------------------------

    /// @notice Limits the reward awarded when withdrawing value.
    /// @param _amountOutSD The amount of expected on the destination chain in SD
    /// @param _reward The initial calculated reward by FeeLib
    /// @return newAmountOutSD The actual amount to be delivered on the destination chain
    /// @return newReward The actual reward after applying any caps
    function _capReward(
        uint64 _amountOutSD,
        uint64 _reward
    ) internal view virtual returns (uint64 newAmountOutSD, uint64 newReward);

    /// @notice Hook called when there is ingress of value into the contract.
    /// @param _from The account from which to obtain the value
    /// @param _amountLD The amount of tokens to get from the account in LD
    /// @return amountSD The actual amount of tokens in SD that got into the Stargate
    function _inflow(address _from, uint256 _amountLD) internal virtual returns (uint64 amountSD);

    /// @notice Hook called when there is egress of value out of the contract.
    /// @return success Whether the outflow was successful
    function _outflow(address _to, uint256 _amountLD) internal virtual returns (bool success);

    /// @notice Hook called when there is egress of value out of the contract.
    /// @dev Reverts with OutflowFailed when the outflow hook fails
    function _safeOutflow(address _to, uint256 _amountLD) internal virtual {
        bool success = _outflow(_to, _amountLD);
        if (!success) revert Stargate_OutflowFailed();
    }

    /// @notice Ensure that the value passed through the message equals the native fee
    /// @dev the native fee should be the same as msg value by default
    /// @dev Reverts with InvalidAmount if the native fee does not match the value passed.
    /// @param _fee The MessagingFee object containing the expected fee
    /// @return The messaging fee object
    function _assertMessagingFee(
        MessagingFee memory _fee,
        uint256 /*_amountInLD*/
    ) internal view virtual returns (MessagingFee memory) {
        if (_fee.nativeFee != msg.value) revert Stargate_InvalidAmount();
        return _fee;
    }

    /// @notice Ensure the msg.value is as expected.
    /// @dev Override this contract to provide a specific validation.
    /// @dev This implementation will revert if value is passed, because we do not expect value except for
    /// @dev the native token when adding to the treasury.
    /// @dev Reverts with InvalidAmount if msg.value > 0
    function _assertMsgValue(uint256 /*_amountLD*/) internal view virtual {
        if (msg.value > 0) revert Stargate_InvalidAmount();
    }

    /// @dev Build the FeeParams object for the FeeLib
    /// @param _dstEid The destination endpoint ID
    /// @param _amountInSD The amount to send in SD
    /// @param _isTaxi Whether this send is riding the bus or taxing
    function _buildFeeParams(
        uint32 _dstEid,
        uint64 _amountInSD,
        bool _isTaxi
    ) internal view virtual returns (FeeParams memory);

    /// @notice Hook called after the inflow of value into the contract by sendToken().
    /// Function meant to be overridden
    // solhint-disable-next-line no-empty-blocks
    function _postInflow(uint64 _amountSD) internal virtual {}

    /// @notice Hook called after the outflow of value out of the contract by receiveToken().
    /// Function meant to be overridden
    // solhint-disable-next-line no-empty-blocks
    function _postOutflow(uint64 _amountSD) internal virtual {}
}
"
    },
    "node_modules/@stargatefinance/stg-evm-v2/src/interfaces/IStargate.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

// Solidity does not support splitting import across multiple lines
// solhint-disable-next-line max-line-length
import { IOFT, SendParam, MessagingFee, MessagingReceipt, OFTReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol";

/// @notice Stargate implementation type.
enum StargateType {
    Pool,
    OFT
}

/// @notice Ticket data for bus ride.
struct Ticket {
    uint72 ticketId;
    bytes passengerBytes;
}

/// @title Interface for Stargate.
/// @notice Defines an API for sending tokens to destination chains.
interface IStargate is IOFT {
    /// @dev This function is same as `send` in OFT interface but returns the ticket data if in the bus ride mode,
    /// which allows the caller to ride and drive the bus in the same transaction.
    function sendToken(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt, Ticket memory ticket);

    /// @notice Returns the Stargate implementation type.
    function stargateType() external pure returns (StargateType);
}
"
    },
    "node_modules/forge-std/src/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
    /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
    /// is the new allowance.
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /// @notice Returns the amount of tokens in existence.
    function totalSupply() external view returns (uint256);

    /// @notice Returns the amount of tokens owned by `account`.
    function balanceOf(address account) external view returns (uint256);

    /// @notice Moves `amount` tokens from the caller's account to `to`.
    function transfer(address to, uint256 amount) external returns (bool);

    /// @notice Returns the remaining number of tokens that `spender` is allowed
    /// to spend on behalf of `owner`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
    /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
    /// `amount` is then deducted from the caller's allowance.
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

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

    /// @notice Returns the symbol of the token.
    function symbol() external view returns (string memory);

    /// @notice Returns the decimal

Tags:
ERC20, Multisig, Burnable, Swap, Liquidity, Upgradeable, Multi-Signature, Factory|addr:0xa7febf3d2c50076eb92ee79ba6083e51873528dd|verified:true|block:23388813|tx:0xc6b8b56bad3ac4f73263a25bf0417db3bd3ae6e4d65e1dc3a45bae4cf3a160a6|first_check:1758191557

Submitted on: 2025-09-18 12:32:39

Comments

Log in to comment.

No comments yet.