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
Submitted on: 2025-09-18 12:32:39
Comments
Log in to comment.
No comments yet.