From 9e972e1b949cf89989cd438e01e5f5bdd1b987b4 Mon Sep 17 00:00:00 2001 From: Tabish Shaikh Date: Thu, 29 Jan 2026 17:02:15 +0530 Subject: [PATCH 1/2] initial commit --- src/ForeignController.sol | 48 +++++++------------------------ src/libraries/LayerZeroLib.sol | 2 +- test/mainnet-fork/LayerZero.t.sol | 4 +-- 3 files changed, 13 insertions(+), 41 deletions(-) diff --git a/src/ForeignController.sol b/src/ForeignController.sol index 2893cd3c..f415a2aa 100644 --- a/src/ForeignController.sol +++ b/src/ForeignController.sol @@ -16,6 +16,8 @@ import { IERC4626 } from "../lib/openzeppelin-contracts/contracts/interfaces/IER import { IPSM3 } from "spark-psm/src/interfaces/IPSM3.sol"; +import { LayerZeroLib } from "./libraries/LayerZeroLib.sol"; + import { IALMProxy } from "./interfaces/IALMProxy.sol"; import { ICCTPLike } from "./interfaces/CCTPInterfaces.sol"; import { IRateLimits } from "./interfaces/IRateLimits.sol"; @@ -68,7 +70,7 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable { bytes32 public constant LIMIT_AAVE_DEPOSIT = keccak256("LIMIT_AAVE_DEPOSIT"); bytes32 public constant LIMIT_AAVE_WITHDRAW = keccak256("LIMIT_AAVE_WITHDRAW"); bytes32 public constant LIMIT_ASSET_TRANSFER = keccak256("LIMIT_ASSET_TRANSFER"); - bytes32 public constant LIMIT_LAYERZERO_TRANSFER = keccak256("LIMIT_LAYERZERO_TRANSFER"); + bytes32 public constant LIMIT_LAYERZERO_TRANSFER = LayerZeroLib.LIMIT_LAYERZERO_TRANSFER; bytes32 public constant LIMIT_PSM_DEPOSIT = keccak256("LIMIT_PSM_DEPOSIT"); bytes32 public constant LIMIT_PSM_WITHDRAW = keccak256("LIMIT_PSM_WITHDRAW"); bytes32 public constant LIMIT_SPARK_VAULT_TAKE = keccak256("LIMIT_SPARK_VAULT_TAKE"); @@ -301,45 +303,15 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable { payable nonReentrant onlyRole(RELAYER) - rateLimited( - keccak256(abi.encode(LIMIT_LAYERZERO_TRANSFER, oftAddress, destinationEndpointId)), - amount - ) { - bytes32 recipient = layerZeroRecipients[destinationEndpointId]; - - require(recipient != bytes32(0), "FC/recipient-not-set"); - - // NOTE: Full integration testing of this logic is not possible without OFTs with - // approvalRequired == true. Add integration testing for this case before - // using in production. - if (ILayerZero(oftAddress).approvalRequired()) { - _approve(ILayerZero(oftAddress).token(), oftAddress, amount); - } - - bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200_000, 0); - - SendParam memory sendParams = SendParam({ - dstEid : destinationEndpointId, - to : recipient, - amountLD : amount, - minAmountLD : 0, - extraOptions : options, - composeMsg : "", - oftCmd : "" + LayerZeroLib.transferTokenLayerZero({ + proxy : proxy, + rateLimits : rateLimits, + oftAddress : oftAddress, + amount : amount, + destinationEndpointId : destinationEndpointId, + layerZeroRecipient : layerZeroRecipients[destinationEndpointId] }); - - // Query the min amount received on the destination chain and set it. - ( ,, OFTReceipt memory receipt ) = ILayerZero(oftAddress).quoteOFT(sendParams); - sendParams.minAmountLD = receipt.amountReceivedLD; - - MessagingFee memory fee = ILayerZero(oftAddress).quoteSend(sendParams, false); - - proxy.doCallWithValue{value: fee.nativeFee}( - oftAddress, - abi.encodeCall(ILayerZero.send, (sendParams, fee, address(proxy))), - fee.nativeFee - ); } /**********************************************************************************************/ diff --git a/src/libraries/LayerZeroLib.sol b/src/libraries/LayerZeroLib.sol index ecb06431..041bb588 100644 --- a/src/libraries/LayerZeroLib.sol +++ b/src/libraries/LayerZeroLib.sol @@ -39,7 +39,7 @@ library LayerZeroLib { amount ); - require(layerZeroRecipient != bytes32(0), "MC/recipient-not-set"); + require(layerZeroRecipient != bytes32(0), "recipient-not-set"); // NOTE: Full integration testing of this logic is not possible without OFTs with // approvalRequired == false. Add integration testing for this case before diff --git a/test/mainnet-fork/LayerZero.t.sol b/test/mainnet-fork/LayerZero.t.sol index 6b5e8748..1abbf180 100644 --- a/test/mainnet-fork/LayerZero.t.sol +++ b/test/mainnet-fork/LayerZero.t.sol @@ -173,7 +173,7 @@ contract MainnetControllerTransferLayerZeroFailureTests is MainnetControllerLaye deal(relayer, fee.nativeFee); vm.prank(relayer); - vm.expectRevert("MC/recipient-not-set"); + vm.expectRevert("recipient-not-set"); mainnetController.transferTokenLayerZero{value: fee.nativeFee}( USDT_OFT, 10_000_000e6, @@ -540,7 +540,7 @@ contract ForeignControllerTransferLayerZeroFailureTests is ArbitrumChainLayerZer deal(relayer, fee.nativeFee); vm.prank(relayer); - vm.expectRevert("FC/recipient-not-set"); + vm.expectRevert("recipient-not-set"); foreignController.transferTokenLayerZero{value: fee.nativeFee}( USDT_OFT, 10_000_000e6, From a8928bb4a439cbc6c0831d3c430a5ee326e2c764 Mon Sep 17 00:00:00 2001 From: Tabish Shaikh Date: Thu, 29 Jan 2026 18:29:53 +0530 Subject: [PATCH 2/2] fix: review --- src/libraries/LayerZeroLib.sol | 2 +- test/mainnet-fork/LayerZero.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/LayerZeroLib.sol b/src/libraries/LayerZeroLib.sol index 318a7bac..1cf67ca3 100644 --- a/src/libraries/LayerZeroLib.sol +++ b/src/libraries/LayerZeroLib.sol @@ -47,7 +47,7 @@ library LayerZeroLib { amount ); - require(layerZeroRecipient != bytes32(0), "recipient-not-set"); + require(layerZeroRecipient != bytes32(0), "LayerZeroLib/recipient-not-set"); // NOTE: Full integration testing of this logic is not possible without OFTs with // approvalRequired == false. Add integration testing for this case before diff --git a/test/mainnet-fork/LayerZero.t.sol b/test/mainnet-fork/LayerZero.t.sol index 1abbf180..d9fa48e6 100644 --- a/test/mainnet-fork/LayerZero.t.sol +++ b/test/mainnet-fork/LayerZero.t.sol @@ -173,7 +173,7 @@ contract MainnetControllerTransferLayerZeroFailureTests is MainnetControllerLaye deal(relayer, fee.nativeFee); vm.prank(relayer); - vm.expectRevert("recipient-not-set"); + vm.expectRevert("LayerZeroLib/recipient-not-set"); mainnetController.transferTokenLayerZero{value: fee.nativeFee}( USDT_OFT, 10_000_000e6, @@ -540,7 +540,7 @@ contract ForeignControllerTransferLayerZeroFailureTests is ArbitrumChainLayerZer deal(relayer, fee.nativeFee); vm.prank(relayer); - vm.expectRevert("recipient-not-set"); + vm.expectRevert("LayerZeroLib/recipient-not-set"); foreignController.transferTokenLayerZero{value: fee.nativeFee}( USDT_OFT, 10_000_000e6,