LayerZeroProvider Diff

作成日 差分は期限切れになりません
28 削除
526 行
69 追加
566 行
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1


pragma solidity ^0.8.20;
pragma solidity ^0.8.20;


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


import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";
import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";
import { MessagingFee, OFTReceipt, SendParam, MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol";
import { MessagingFee, OFTReceipt, SendParam, MessagingReceipt } 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 { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol";


import { IStargate } from "@stargatefinance/stg-evm-v2/src/interfaces/IStargate.sol";
import { IStargate } from "@stargatefinance/stg-evm-v2/src/interfaces/IStargate.sol";


import "../event/EventEmitter.sol";
import "../event/EventEmitter.sol";
import "../data/DataStore.sol";
import "../data/DataStore.sol";
import "../utils/Cast.sol";
import "../utils/Cast.sol";
import "../gas/GasUtils.sol";
import "../gas/GasUtils.sol";
import "../deposit/DepositStoreUtils.sol";
import "../deposit/DepositStoreUtils.sol";
import "../glv/GlvUtils.sol";
import "../glv/GlvUtils.sol";


import "./IMultichainProvider.sol";
import "./IMultichainProvider.sol";
import "./IMultichainGmRouter.sol";
import "./IMultichainGmRouter.sol";
import "./IMultichainGlvRouter.sol";
import "./IMultichainGlvRouter.sol";
import "./IMultichainOrderRouter.sol";
import "./IMultichainOrderRouter.sol";


import "./MultichainVault.sol";
import "./MultichainVault.sol";
import "./MultichainUtils.sol";
import "./MultichainUtils.sol";




/**
/**
* @title LayerZeroProvider
* @title LayerZeroProvider
* Bridging In is done throught lzCompose (receives tokens + encoded message from a source chain)
* Bridging In is done throught lzCompose (receives tokens + encoded message from a source chain)
* Bridging Out is done through bridgeOut (sends tokens to a source chain)
* Bridging Out is done through bridgeOut (sends tokens to a source chain)
* Defines lzCompose function which:
* Defines lzCompose function which:
* - is called by the Stargate executor after tokens are delivered to this contract
* - is called by the Stargate executor after tokens are delivered to this contract
* - forwards the received tokens to MultichainVault and increases user's multichain balance
* - forwards the received tokens to MultichainVault and increases user's multichain balance
* Defines bridgeOut function which:
* Defines bridgeOut function which:
* - sends tokens to the Stargate executor for bridging out to the source chain
* - sends tokens to the Stargate executor for bridging out to the source chain
*/
*/
contract LayerZeroProvider is IMultichainProvider, ILayerZeroComposer, RoleModule {
contract LayerZeroProvider is IMultichainProvider, ILayerZeroComposer, RoleModule {
struct BridgeOutCache {
struct BridgeOutCache {
uint32 dstEid;
uint32 dstEid;
uint256 valueToSend;
uint256 valueToSend;
MessagingReceipt msgReceipt;
MessagingReceipt msgReceipt;
SendParam sendParam;
SendParam sendParam;
MessagingFee messagingFee;
MessagingFee messagingFee;
OFTReceipt receipt;
OFTReceipt receipt;
}
}


DataStore public immutable dataStore;
DataStore public immutable dataStore;
EventEmitter public immutable eventEmitter;
EventEmitter public immutable eventEmitter;
MultichainVault public immutable multichainVault;
MultichainVault public immutable multichainVault;
IMultichainGmRouter public immutable multichainGmRouter;
IMultichainGmRouter public immutable multichainGmRouter;
IMultichainGlvRouter public immutable multichainGlvRouter;
IMultichainGlvRouter public immutable multichainGlvRouter;
IMultichainOrderRouter public immutable multichainOrderRouter;
IMultichainOrderRouter public immutable multichainOrderRouter;


constructor(
constructor(
DataStore _dataStore,
DataStore _dataStore,
RoleStore _roleStore,
RoleStore _roleStore,
EventEmitter _eventEmitter,
EventEmitter _eventEmitter,
MultichainVault _multichainVault,
MultichainVault _multichainVault,
IMultichainGmRouter _multichainGmRouter,
IMultichainGmRouter _multichainGmRouter,
IMultichainGlvRouter _multichainGlvRouter,
IMultichainGlvRouter _multichainGlvRouter,
IMultichainOrderRouter _multichainOrderRouter
IMultichainOrderRouter _multichainOrderRouter
) RoleModule(_roleStore) {
) RoleModule(_roleStore) {
dataStore = _dataStore;
dataStore = _dataStore;
eventEmitter = _eventEmitter;
eventEmitter = _eventEmitter;
multichainVault = _multichainVault;
multichainVault = _multichainVault;
multichainGmRouter = _multichainGmRouter;
multichainGmRouter = _multichainGmRouter;
multichainGlvRouter = _multichainGlvRouter;
multichainGlvRouter = _multichainGlvRouter;
multichainOrderRouter = _multichainOrderRouter;
multichainOrderRouter = _multichainOrderRouter;
}
}


/**
/**
* @notice Called by Stargate after tokens have been delivered to this contract.
* @notice Called by Stargate after tokens have been delivered to this contract.
* @dev Handles the receipt of bridged tokens and optionally executes a deposit action.
* @dev Handles the receipt of bridged tokens and optionally executes a deposit action.
*
*
* @dev If a user bridges tokens with deposit data, and already has sufficient funds in their multichain balance,
* @dev If a user bridges tokens with deposit data, and already has sufficient funds in their multichain balance,
* it is possible for multiple bridge transactions to result in multiple deposits (i.e. double mints).
* it is possible for multiple bridge transactions to result in multiple deposits (i.e. double mints).
* For example, if a user bridges 10 WETH and 20,000 USDC, both with deposit data, and already has enough funds,
* For example, if a user bridges 10 WETH and 20,000 USDC, both with deposit data, and already has enough funds,
* both bridge transactions could result in a deposit.
* both bridge transactions could result in a deposit.
*
*
* @dev It is recommended that the interface or frontend enforces that users only bridge amounts that would not
* @dev It is recommended that the interface or frontend enforces that users only bridge amounts that would not
* result in double deposits.
* result in double deposits.
*
*
* @param from The address of the sender (i.e. Stargate address, not user's address).
* @param from The address of the sender (i.e. Stargate address, not user's address).
* param guid A global unique identifier for tracking the packet.
* param guid A global unique identifier for tracking the packet.
* @param message Encoded message. Contains the params needed to record the deposit (account, srcChainId)
* @param message Encoded message. Contains the params needed to record the deposit (account, srcChainId)
* param executor The address of the Executor.
* param executor The address of the Executor.
* param extraData Any extra data or options to trigger on receipt.
* param extraData Any extra data or options to trigger on receipt.
*/
*/
function lzCompose(
function lzCompose(
address from,
address from,
bytes32 /*guid*/,
bytes32 /*guid*/,
bytes calldata message,
bytes calldata message,
address /*executor*/,
address /*executor*/,
bytes calldata /*extraData*/
bytes calldata /*extraData*/
) external payable {
) external payable {
MultichainUtils.validateMultichainProvider(dataStore, from);
MultichainUtils.validateMultichainProvider(dataStore, from);
MultichainUtils.validateMultichainEndpoint(dataStore, msg.sender);
MultichainUtils.validateMultichainEndpoint(dataStore, msg.sender);


/// @dev The `account` field is user-supplied and not validated; any address may be provided by the sender
/// @dev The `account` field is user-supplied and not validated; any address may be provided by the sender
// msg.sender and `from` need to be validated for `amountLD` and `token` to not be spoofable
// msg.sender and `from` need to be validated for `amountLD` and `token` to not be spoofable
// additionally, the lzReceive, _credit and _debit functions of the OFT should be checked for any overrides
// additionally, the lzReceive, _credit and _debit functions of the OFT should be checked for any overrides
// before the `amountLD` and `token` values can be trusted
// before the `amountLD` and `token` values can be trusted
(address account, uint256 srcChainId, uint256 amountLD, bytes memory data) = _decodeLzComposeMsg(message);
(address account, uint256 srcChainId, uint256 amountLD, bytes memory data) = _decodeLzComposeMsg(message);


address token = IStargate(from).token();
address token = IStargate(from).token();

if (token == address(0x0)) {
if (token == address(0x0)) {
// `from` is StargatePoolNative
// `from` is StargatePoolNative
TokenUtils.depositAndSendWrappedNativeToken(dataStore, address(multichainVault), amountLD);
TokenUtils.depositAndSendWrappedNativeToken(dataStore, address(multichainVault), amountLD);


// if token is ETH then we need to use WNT
// if token is ETH then we need to use WNT
token = TokenUtils.wnt(dataStore);
token = TokenUtils.wnt(dataStore);
} else {
} else {
// `from` is e.g. StargatePoolUSDC
// `from` is e.g. StargatePoolUSDC
TokenUtils.transfer(dataStore, token, address(multichainVault), amountLD);
TokenUtils.transfer(dataStore, token, address(multichainVault), amountLD);
}
}
MultichainUtils.recordBridgeIn(
MultichainUtils.recordBridgeIn(
dataStore,
dataStore,
eventEmitter,
eventEmitter,
multichainVault,
multichainVault,
this,
this,
token,
token,
account,
account,
srcChainId
srcChainId
);
);

// Handle native token top-up before other actions to ensure the user has the ability to
// pay relay and execution fees required by subsequent actions (Deposit, Withdrawal, etc.)
uint256 expectedNativeValue = 0;
if (data.length != 0) {
(, uint256 decodedExpectedNativeValue, ) = abi.decode(data, (ActionType, uint256, bytes));
expectedNativeValue = decodedExpectedNativeValue;
}
if (expectedNativeValue != 0 || msg.value != 0) {
_handleNativeTopUp(account, srcChainId, expectedNativeValue);
}



// note that for these functions, the remaining gas must be sufficient
// note that for these functions, the remaining gas must be sufficient
// otherwise the function will revert
// otherwise the function will revert
// if the action cannot be completed due to gas issues, the user would
// if the action cannot be completed due to gas issues, the user would
// need to manually call the function with the right amount of gas
// need to manually call the function with the right amount of gas
// srcChainId could be zero when returned from _decodeLzComposeMsg, skip follow up actions in this case
// srcChainId could be zero when returned from _decodeLzComposeMsg, skip follow up actions in this case
if (srcChainId != 0 && data.length != 0) {
if (srcChainId != 0 && data.length != 0) {
(ActionType actionType, bytes memory actionData) = abi.decode(data, (ActionType, bytes));
(ActionType actionType, , bytes memory actionData) = abi.decode(data, (ActionType, uint256, bytes));


// this event is emitted even though the actionType may not be valid
// this event is emitted even though the actionType may not be valid
MultichainEventUtils.emitMultichainBridgeAction(eventEmitter, address(this), account, srcChainId, uint256(actionType));
MultichainEventUtils.emitMultichainBridgeAction(eventEmitter, address(this), account, srcChainId, uint256(actionType));


/// @dev relayParams.fee.feeToken must default to WNT when feeSwapPath is not being used
/// @dev relayParams.fee.feeToken must default to WNT when feeSwapPath is not being used
/// otherwise reverts with UnexpectedRelayFeeToken
/// otherwise reverts with UnexpectedRelayFeeToken
if (actionType == ActionType.Deposit) {
if (actionType == ActionType.Deposit) {
_handleDeposit(account, srcChainId, actionType, actionData);
_handleDeposit(account, srcChainId, actionType, actionData);
} else if (actionType == ActionType.GlvDeposit) {
} else if (actionType == ActionType.GlvDeposit) {
_handleGlvDeposit(account, srcChainId, actionType, actionData);
_handleGlvDeposit(account, srcChainId, actionType, actionData);
} else if (actionType == ActionType.SetTraderReferralCode) {
} else if (actionType == ActionType.SetTraderReferralCode) {
_handleSetTraderReferralCode(account, srcChainId, actionType, actionData);
_handleSetTraderReferralCode(account, srcChainId, actionType, actionData);
} else if (actionType == ActionType.RegisterCode) {
_handleRegisterCode(account, srcChainId, actionType, actionData);
} else if (actionType == ActionType.Withdrawal) {
} else if (actionType == ActionType.Withdrawal) {
_handleWithdrawal(account, srcChainId, actionType, actionData);
_handleWithdrawal(account, srcChainId, actionType, actionData);
} else if (actionType == ActionType.GlvWithdrawal) {
} else if (actionType == ActionType.GlvWithdrawal) {
_handleGlvWithdrawal(account, srcChainId, actionType, actionData);
_handleGlvWithdrawal(account, srcChainId, actionType, actionData);
}
}
}
}
}
}


/**
/**
* Bridges tokens from the current chain to a source chain using Stargate protocol
* Bridges tokens from the current chain to a source chain using Stargate protocol
* @dev Processes a cross-chain transfer of tokens from user's multichain balance
* @dev Processes a cross-chain transfer of tokens from user's multichain balance
* @dev Called by MultichainTransferRouter
* @dev Called by MultichainTransferRouter
* This function:
* This function:
* 1. Approves tokens to be spent by Stargate
* 1. Approves tokens to be spent by Stargate
* 2. Prepares the necessary parameters and quotes fees
* 2. Prepares the necessary parameters and quotes fees
* 3. Transfers the bridging fee (in WNT) from user's multichain balance
* 3. Transfers the bridging fee (in WNT) from user's multichain balance
* 4. Unwraps WNT to ETH to pay for the cross-chain fee
* 4. Unwraps WNT to ETH to pay for the cross-chain fee
* 5. Transfers the tokens to be bridged from user's multichain balance
* 5. Transfers the tokens to be bridged from user's multichain balance
* 6. Calls Stargate to initiate the cross-chain transfer
* 6. Calls Stargate to initiate the cross-chain transfer
*
*
* @param params A struct containing:
* @param params A struct containing:
* - provider: Address of the Stargate pool
* - provider: Address of the Stargate pool
* - account: User account bridging tokens
* - account: User account bridging tokens
* - token: Address of token being bridged
* - token: Address of token being bridged
* - amount: Amount of tokens to bridge
* - amount: Amount of tokens to bridge
* - srcChainId: Source chain ID (for multichain balance accounting)
* - srcChainId: Source chain ID (for multichain balance accounting)
* - data: ABI-encoded destination endpoint ID (dstEid)
* - data: ABI-encoded destination endpoint ID (dstEid)
* @return The amount of tokens bridged out (may be slightly different from params.amount after LZ precision/path limits adjustments)
* @return The amount of tokens bridged out (may be slightly different from params.amount after LZ precision/path limits adjustments)
*/
*/
function bridgeOut(address account, uint256 srcChainId, IRelayUtils.BridgeOutParams memory params) external onlyController returns (uint256) {
function bridgeOut(address account, uint256 srcChainId, IRelayUtils.BridgeOutParams memory params) external onlyController returns (uint256) {
IStargate stargate = IStargate(params.provider);
IStargate stargate = IStargate(params.provider);


address wnt = dataStore.getAddress(Keys.WNT);
address wnt = dataStore.getAddress(Keys.WNT);


if (stargate.token() == address(0x0)) {
if (stargate.token() == address(0x0)) {
// `stargate` is StargatePoolNative
// `stargate` is StargatePoolNative
if (params.token != wnt) {
if (params.token != wnt) {
revert Errors.InvalidBridgeOutToken(params.token);
revert Errors.InvalidBridgeOutToken(params.token);
}
}
} else {
} else {
// `stargate` is e.g. StargatePoolUSDC
// `stargate` is e.g. StargatePoolUSDC
if (params.token != stargate.token()) {
if (params.token != stargate.token()) {
revert Errors.InvalidBridgeOutToken(params.token);
revert Errors.InvalidBridgeOutToken(params.token);
}
}
}
}


BridgeOutCache memory cache;
BridgeOutCache memory cache;
cache.dstEid = abi.decode(params.data, (uint32));
cache.dstEid = abi.decode(params.data, (uint32));


if (srcChainId != dataStore.getUint(Keys.eidToSrcChainId(cache.dstEid))) {
if (srcChainId != dataStore.getUint(Keys.eidToSrcChainId(cache.dstEid))) {
revert Errors.InvalidEid(cache.dstEid);
revert Errors.InvalidEid(cache.dstEid);
}
}


(cache.valueToSend, cache.sendParam, cache.messagingFee, cache.receipt) = prepareSend(
(cache.valueToSend, cache.sendParam, cache.messagingFee, cache.receipt) = prepareSend(
stargate,
stargate,
params.amount,
params.amount,
params.minAmountOut,
params.minAmountOut,
account,
account,
cache.dstEid
cache.dstEid
);
);


// LZ/Stargate would round down the `amount` to 6 decimals precision / apply path limits
// LZ/Stargate would round down the `amount` to 6 decimals precision / apply path limits
params.amount = cache.receipt.amountSentLD;
params.amount = cache.receipt.amountSentLD;


// transferOut bridging fee amount of wnt from user's multichain balance into this contract
// transferOut bridging fee amount of wnt from user's multichain balance into this contract
// for StargatePoolNative, amountSentLD is added on top of the bridging fee
// for StargatePoolNative, amountSentLD is added on top of the bridging fee
MultichainUtils.transferOut(
MultichainUtils.transferOut(
dataStore,
dataStore,
eventEmitter,
eventEmitter,
multichainVault,
multichainVault,
wnt, // token
wnt, // token
account,
account,
address(this), // receiver
address(this), // receiver
cache.valueToSend, // bridge out fee (+ amountSentLD for native token transfers)
cache.valueToSend, // bridge out fee (+ amountSentLD for native token transfers)
srcChainId
srcChainId
);
);


IWNT(wnt).withdraw(cache.valueToSend);
IWNT(wnt).withdraw(cache.valueToSend);


// if Stagrate.token() is the ZeroAddress:
// if Stagrate.token() is the ZeroAddress:
// - amountSentLD was already added to valueToSend and transferred/unwrapped with the bridging fee
// - amountSentLD was already added to valueToSend and transferred/unwrapped with the bridging fee
// - approval is not needed (since native tokens are being bridged)
// - approval is not needed (since native tokens are being bridged)
if (stargate.token() != address(0x0)) {
if (stargate.token() != address(0x0)) {
// `stargate` is e.g. StargatePoolUSDC
// `stargate` is e.g. StargatePoolUSDC
// transferOut amount of tokens from user's multichain balance into this contract
// transferOut amount of tokens from user's multichain balance into this contract
MultichainUtils.transferOut(
MultichainUtils.transferOut(
dataStore,
dataStore,
eventEmitter,
eventEmitter,
multichainVault,
multichainVault,
params.token,
params.token,
account,
account,
address(this), // receiver
address(this), // receiver
params.amount,
params.amount,
srcChainId
srcChainId
);
);


IERC20(params.token).approve(params.provider, params.amount);
IERC20(params.token).approve(params.provider, params.amount);
}
}


(cache.msgReceipt, /* OFTReceipt memory oftReceipt */) = stargate.send{ value: cache.valueToSend }(
(cache.msgReceipt, /* OFTReceipt memory oftReceipt */) = stargate.send{ value: cache.valueToSend }(
cache.sendParam,
cache.sendParam,
cache.messagingFee,
cache.messagingFee,
address(this) // refundAddress
address(this) // refundAddress
);
);


// fee refunds are send back to this contract, converted to wrapped native token,
// fee refunds are send back to this contract, converted to wrapped native token,
// sent to multichainVault and user's multichain balance is increased
// sent to multichainVault and user's multichain balance is increased
if (cache.msgReceipt.fee.nativeFee < cache.messagingFee.nativeFee) {
if (cache.msgReceipt.fee.nativeFee < cache.messagingFee.nativeFee) {
TokenUtils.depositAndSendWrappedNativeToken(
TokenUtils.depositAndSendWrappedNativeToken(
dataStore,
dataStore,
address(multichainVault), // receiver
address(multichainVault), // receiver
cache.messagingFee.nativeFee - cache.msgReceipt.fee.nativeFee // refund amount
cache.messagingFee.nativeFee - cache.msgReceipt.fee.nativeFee // refund amount
);
);
MultichainUtils.recordTransferIn(
MultichainUtils.recordTransferIn(
dataStore,
dataStore,
eventEmitter,
eventEmitter,
multichainVault,
multichainVault,
wnt, // token
wnt, // token
account,
account,
0 // srcChainId
0 // srcChainId
);
);
}
}


return params.amount;
return params.amount;
}
}


function prepareSend(
function prepareSend(
IStargate stargate,
IStargate stargate,
uint256 amount,
uint256 amount,
uint256 minAmountOut,
uint256 minAmountOut,
address receiver,
address receiver,
uint32 _dstEid
uint32 _dstEid
)
)
private
private
view
view
returns (
returns (
uint256 valueToSend,
uint256 valueToSend,
SendParam memory sendParam,
SendParam memory sendParam,
MessagingFee memory messagingFee,
MessagingFee memory messagingFee,
OFTReceipt memory receipt
OFTReceipt memory receipt
)
)
{
{
sendParam = SendParam({
sendParam = SendParam({
dstEid: _dstEid,
dstEid: _dstEid,
to: Cast.toBytes32(receiver),
to: Cast.toBytes32(receiver),
amountLD: amount,
amountLD: amount,
minAmountLD: minAmountOut,
minAmountLD: minAmountOut,
extraOptions: bytes(""),
extraOptions: bytes(""),
composeMsg: bytes(""),
composeMsg: bytes(""),
oftCmd: ""
oftCmd: ""
});
});


(, , receipt) = stargate.quoteOFT(sendParam);
(, , receipt) = stargate.quoteOFT(sendParam);
sendParam.minAmountLD = receipt.amountReceivedLD;
sendParam.minAmountLD = receipt.amountReceivedLD;


messagingFee = stargate.quoteSend(sendParam, false);
messagingFee = stargate.quoteSend(sendParam, false);
valueToSend = messagingFee.nativeFee;
valueToSend = messagingFee.nativeFee;


if (stargate.token() == address(0x0)) {
if (stargate.token() == address(0x0)) {
valueToSend += receipt.amountSentLD;
valueToSend += receipt.amountSentLD;
}
}
}
}


function _decodeLzComposeMsg(bytes calldata message) private view returns (address, uint256, uint256, bytes memory) {
function _decodeLzComposeMsg(bytes calldata message) private view returns (address, uint256, uint256, bytes memory) {
uint256 amountLD = OFTComposeMsgCodec.amountLD(message);
uint256 amountLD = OFTComposeMsgCodec.amountLD(message);


uint32 srcEid = OFTComposeMsgCodec.srcEid(message);
uint32 srcEid = OFTComposeMsgCodec.srcEid(message);
uint256 srcChainId = dataStore.getUint(Keys.eidToSrcChainId(srcEid));
uint256 srcChainId = dataStore.getUint(Keys.eidToSrcChainId(srcEid));


bytes memory composeMessage = OFTComposeMsgCodec.composeMsg(message);
bytes memory composeMessage = OFTComposeMsgCodec.composeMsg(message);
(address account, bytes memory data) = abi.decode(composeMessage, (address, bytes));
(address account, bytes memory data) = abi.decode(composeMessage, (address, bytes));


return (account, srcChainId, amountLD, data);
return (account, srcChainId, amountLD, data);
}
}


function _areValidTransferRequests(IRelayUtils.TransferRequests memory transferRequests) private pure returns (bool) {
function _areValidTransferRequests(IRelayUtils.TransferRequests memory transferRequests) private pure returns (bool) {
if (
if (
transferRequests.tokens.length != transferRequests.receivers.length ||
transferRequests.tokens.length != transferRequests.receivers.length ||
transferRequests.tokens.length != transferRequests.amounts.length
transferRequests.tokens.length != transferRequests.amounts.length
) {
) {
return false;
return false;
}
}
for (uint256 i = 0; i < transferRequests.tokens.length; i++) {
for (uint256 i = 0; i < transferRequests.tokens.length; i++) {
if (
if (
transferRequests.tokens[i] == address(0) ||
transferRequests.tokens[i] == address(0) ||
transferRequests.receivers[i] == address(0) ||
transferRequests.receivers[i] == address(0) ||
transferRequests.amounts[i] == 0
transferRequests.amounts[i] == 0
) {
) {
return false;
return false;
}
}
}
}
return true;
return true;
}
}


function _handleNativeTopUp(
address account,
uint256 srcChainId,
uint256 expectedNativeValue
) private {
// Enforce msg.value matches expected native top-up amount
// Note: This may cause bridged tokens to be locked if executor underpays,
// but other executors or users can retry lzCompose with correct msg.value
if (msg.value < expectedNativeValue) {
revert Errors.InsufficientNativeTokenAmount(msg.value, expectedNativeValue);
}

// Allow receiving native tokens alongside bridged tokens to cover execution fees
// msg.value represents ONLY the top-up amount specified in lzComposeOption (not the bridged tokens)
// Bridged tokens are already transferred to this contract separately via Stargate's flow
TokenUtils.depositAndSendWrappedNativeToken(dataStore, address(multichainVault), msg.value);
MultichainUtils.recordBridgeIn(
dataStore,
eventEmitter,
multichainVault,
this,
TokenUtils.wnt(dataStore),
account,
srcChainId
);
}

/// @dev long/short tokens are deposited from user's multichain balance
/// @dev long/short tokens are deposited from user's multichain balance
/// GM tokens are minted and transferred to user's multichain balance
/// GM tokens are minted and transferred to user's multichain balance
function _handleDeposit(
function _handleDeposit(
address account,
address account,
uint256 srcChainId,
uint256 srcChainId,
ActionType actionType,
ActionType actionType,
bytes memory actionData
bytes memory actionData
) private {
) private {
(
(
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.TransferRequests memory transferRequests,
IRelayUtils.TransferRequests memory transferRequests,
IDepositUtils.CreateDepositParams memory depositParams
IDepositUtils.CreateDepositParams memory depositParams
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IDepositUtils.CreateDepositParams));
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IDepositUtils.CreateDepositParams));


if (_areValidTransferRequests(transferRequests)) {
if (_areValidTransferRequests(transferRequests)) {
uint256 estimatedGasLimit = GasUtils.estimateCreateDepositGasLimit(dataStore);
uint256 estimatedGasLimit = GasUtils.estimateCreateDepositGasLimit(dataStore);
_validateGasLeft(estimatedGasLimit);
_validateGasLeft(estimatedGasLimit);


try multichainGmRouter.createDeposit(
try multichainGmRouter.createDeposit(
relayParams,
relayParams,
account,
account,
srcChainId,
srcChainId,
transferRequests,
transferRequests,
depositParams
depositParams
) {
) {
} catch Error(string memory reason) {
} catch Error(string memory reason) {
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
} catch (bytes memory reasonBytes) {
} catch (bytes memory reasonBytes) {
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
}
}
}
}
}
}


/// @dev long/short/GM tokens are deposited from user's multichain balance
/// @dev long/short/GM tokens are deposited from user's multichain balance
/// GLV tokens are minted and transferred to user's multichain balance
/// GLV tokens are minted and transferred to user's multichain balance
function _handleGlvDeposit(
function _handleGlvDeposit(
address account,
address account,
uint256 srcChainId,
uint256 srcChainId,
ActionType actionType,
ActionType actionType,
bytes memory actionData
bytes memory actionData
) private {
) private {
(
(
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.TransferRequests memory transferRequests,
IRelayUtils.TransferRequests memory transferRequests,
IGlvDepositUtils.CreateGlvDepositParams memory glvDepositParams
IGlvDepositUtils.CreateGlvDepositParams memory glvDepositParams
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IGlvDepositUtils.CreateGlvDepositParams));
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IGlvDepositUtils.CreateGlvDepositParams));


if (_areValidTransferRequests(transferRequests)) {
if (_areValidTransferRequests(transferRequests)) {
uint256 estimatedGasLimit = GasUtils.estimateCreateGlvDepositGasLimit(dataStore);
uint256 estimatedGasLimit = GasUtils.estimateCreateGlvDepositGasLimit(dataStore);
_validateGasLeft(estimatedGasLimit);
_validateGasLeft(estimatedGasLimit);


try multichainGlvRouter.createGlvDeposit(
try multichainGlvRouter.createGlvDeposit(
relayParams,
relayParams,
account,
account,
srcChainId,
srcChainId,
transferRequests,
transferRequests,
glvDepositParams
glvDepositParams
) {
) {
} catch Error(string memory reason) {
} catch Error(string memory reason) {
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
} catch (bytes memory reasonBytes) {
} catch (bytes memory reasonBytes) {
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
}
}
}
}
}
}


/// @dev `account` is expected to be `msg.sender` from the source chain, as
/// @dev `account` is expected to be `msg.sender` from the source chain, as
/// MultichainOrderRouter would use it to validate the signature.
/// MultichainOrderRouter would use it to validate the signature.
function _handleSetTraderReferralCode(
function _handleSetTraderReferralCode(
address account,
address account,
uint256 srcChainId,
uint256 srcChainId,
ActionType actionType,
ActionType actionType,
bytes memory actionData
bytes memory actionData
) private {
) private {
(
(
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.RelayParams memory relayParams,
bytes32 referralCode
bytes32 referralCode
) = abi.decode(actionData, (IRelayUtils.RelayParams, bytes32));
) = abi.decode(actionData, (IRelayUtils.RelayParams, bytes32));


uint256 estimatedGasLimit = GasUtils.estimateSetTraderReferralCodeGasLimit(dataStore);
uint256 estimatedGasLimit = GasUtils.estimateSetTraderReferralCodeGasLimit(dataStore);
_validateGasLeft(estimatedGasLimit);
_validateGasLeft(estimatedGasLimit);


try multichainOrderRouter.setTraderReferralCode(relayParams, account, srcChainId, referralCode) {
try multichainOrderRouter.setTraderReferralCode(relayParams, account, srcChainId, referralCode) {
} catch Error(string memory reason) {
} catch Error(string memory reason) {
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
} catch (bytes memory reasonBytes) {
} catch (bytes memory reasonBytes) {
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
}
}
}
}


/// @dev `account` is expected to be `msg.sender` from the source chain, as
/// MultichainOrderRouter would use it to validate the signature.
function _handleRegisterCode(
address account,
uint256 srcChainId,
ActionType actionType,
bytes memory actionData
) private {
(
IRelayUtils.RelayParams memory relayParams,
bytes32 referralCode
) = abi.decode(actionData, (IRelayUtils.RelayParams, bytes32));

uint256 estimatedGasLimit = GasUtils.estimateRegisterCodeGasLimit(dataStore);
_validateGasLeft(estimatedGasLimit);

try multichainOrderRouter.registerCode(relayParams, account, srcChainId, referralCode) {
} catch Error(string memory reason) {
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
} catch (bytes memory reasonBytes) {
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
Text moved from lines 502-504
}
}

function _handleWithdrawal(
function _handleWithdrawal(
address account,
address account,
uint256 srcChainId,
uint256 srcChainId,
ActionType actionType,
ActionType actionType,
bytes memory actionData
bytes memory actionData
) private {
) private {
(
(
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.TransferRequests memory transferRequests,
IRelayUtils.TransferRequests memory transferRequests,
IWithdrawalUtils.CreateWithdrawalParams memory withdrawalParams
IWithdrawalUtils.CreateWithdrawalParams memory withdrawalParams
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IWithdrawalUtils.CreateWithdrawalParams));
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IWithdrawalUtils.CreateWithdrawalParams));


if (_areValidTransferRequests(transferRequests)) {
if (_areValidTransferRequests(transferRequests)) {
uint256 estimatedGasLimit = GasUtils.estimateCreateWithdrawalGasLimit(dataStore);
uint256 estimatedGasLimit = GasUtils.estimateCreateWithdrawalGasLimit(dataStore);
_validateGasLeft(estimatedGasLimit);
_validateGasLeft(estimatedGasLimit);


try multichainGmRouter.createWithdrawal(
try multichainGmRouter.createWithdrawal(
relayParams,
relayParams,
account,
account,
srcChainId,
srcChainId,
transferRequests,
transferRequests,
withdrawalParams
withdrawalParams
) {
) {
} catch Error(string memory reason) {
} catch Error(string memory reason) {
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
} catch (bytes memory reasonBytes) {
} catch (bytes memory reasonBytes) {
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
}
}
}
}
}
}


function _handleGlvWithdrawal(
function _handleGlvWithdrawal(
address account,
address account,
uint256 srcChainId,
uint256 srcChainId,
ActionType actionType,
ActionType actionType,
bytes memory actionData
bytes memory actionData
) private {
) private {
(
(
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.RelayParams memory relayParams,
IRelayUtils.TransferRequests memory transferRequests,
IRelayUtils.TransferRequests memory transferRequests,
IGlvWithdrawalUtils.CreateGlvWithdrawalParams memory glvWithdrawalParams
IGlvWithdrawalUtils.CreateGlvWithdrawalParams memory glvWithdrawalParams
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IGlvWithdrawalUtils.CreateGlvWithdrawalParams));
) = abi.decode(actionData, (IRelayUtils.RelayParams, IRelayUtils.TransferRequests, IGlvWithdrawalUtils.CreateGlvWithdrawalParams));


if (_areValidTransferRequests(transferRequests)) {
if (_areValidTransferRequests(transferRequests)) {
uint256 estimatedGasLimit = GasUtils.estimateCreateGlvWithdrawalGasLimit(dataStore);
uint256 estimatedGasLimit = GasUtils.estimateCreateGlvWithdrawalGasLimit(dataStore);
_validateGasLeft(estimatedGasLimit);
_validateGasLeft(estimatedGasLimit);


try multichainGlvRouter.createGlvWithdrawal(
try multichainGlvRouter.createGlvWithdrawal(
relayParams,
relayParams,
account,
account,
srcChainId,
srcChainId,
transferRequests,
transferRequests,
glvWithdrawalParams
glvWithdrawalParams
) {
) {
} catch Error(string memory reason) {
} catch Error(string memory reason) {
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
} catch (bytes memory reasonBytes) {
} catch (bytes memory reasonBytes) {
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
(string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
MultichainEventUtils.emitMultichainBridgeActionFailed(eventEmitter, address(this), account, srcChainId, uint256(actionType), reason);
}
}
}
}
}
}


function _validateGasLeft(uint256 estimatedGasLimit) internal view {
function _validateGasLeft(uint256 estimatedGasLimit) internal view {
uint256 gas = gasleft();
uint
if (gas < estimatedGasLimit) {
revert Errors.InsufficientGasLeft(gas, estimatedGasLimit);
Text moved to lines 498-500
}
}

/**
* @notice Withdraws tokens which have been locked in this contract due to potential errors in lzCompose (e.g. incorrect message format).
* @dev Callable through the timelock contract.
* @param token The address of the token to withdraw.
* @param receiver The address receiving the withdrawn tokens.
* @param amount The amount of tokens to withdraw.
*/
function withdrawTokens(address token, address receiver, uint256 amount) external onlyController {
if (amount == 0) {
revert Errors.EmptyWithdrawalAmount();
}

if (token == address(0)) {
TokenUtils.sendNativeToken(dataStore, receiver, amount);
} else {
TokenUtils.transfer(dataStore, token, receiver, amount);
}
}

/// @dev Accept ETH from StargatePoolNative and when unwrapping WNT
receive() external payable {}
}