From 0c9c1e7971ad7ff388e9ccc85b156e0f3486dadf Mon Sep 17 00:00:00 2001 From: Ihor Farion Date: Tue, 21 Oct 2025 17:11:04 -0700 Subject: [PATCH 1/6] fix Signed-off-by: Ihor Farion --- ...cutor.sol => ArbitraryEVMFlowExecutor.sol} | 86 +++-------------- .../SponsoredCCTPDstPeriphery.sol | 60 ++++++------ .../mintburn/sponsored-oft/DstOFTHandler.sol | 92 +++++++++---------- 3 files changed, 79 insertions(+), 159 deletions(-) rename contracts/periphery/mintburn/{ArbitraryActionFlowExecutor.sol => ArbitraryEVMFlowExecutor.sol} (71%) diff --git a/contracts/periphery/mintburn/ArbitraryActionFlowExecutor.sol b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol similarity index 71% rename from contracts/periphery/mintburn/ArbitraryActionFlowExecutor.sol rename to contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol index 7af2a49d9..e7aa76d20 100644 --- a/contracts/periphery/mintburn/ArbitraryActionFlowExecutor.sol +++ b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol @@ -12,13 +12,13 @@ import { MulticallHandler } from "../../handlers/MulticallHandler.sol"; import { BPS_SCALAR } from "./Constants.sol"; /** - * @title ArbitraryActionFlowExecutor + * @title ArbitraryEVMFlowExecutor * @notice Base contract for executing arbitrary action sequences using MulticallHandler * @dev This contract provides shared functionality for both OFT and CCTP handlers to execute * arbitrary actions on HyperEVM via MulticallHandler, with optional transfer to HyperCore. * @custom:security-contact bugs@across.to */ -abstract contract ArbitraryActionFlowExecutor { +abstract contract ArbitraryEVMFlowExecutor { using SafeERC20 for IERC20; /// @notice Compressed call struct (no value field to save gas) @@ -51,29 +51,25 @@ abstract contract ArbitraryActionFlowExecutor { * @param quoteNonce Unique nonce for this quote * @param maxBpsToSponsor Maximum basis points to sponsor * @param initialToken Token to transfer to MulticallHandler - * @param finalRecipient Final recipient address * @param finalToken Expected final token after actions * @param actionData Encoded actions: abi.encode(CompressedCall[] calls) - * @param transferToCore Whether to transfer result to HyperCore - * @param extraFeesToSponsor Extra fees to sponsor + * @param extraFeesToSponsorTokenIn Extra fees to sponsor in initialToken */ function _executeArbitraryActionFlow( uint256 amount, bytes32 quoteNonce, uint256 maxBpsToSponsor, address initialToken, - address finalRecipient, address finalToken, bytes memory actionData, - bool transferToCore, - uint256 extraFeesToSponsor - ) internal { + uint256 extraFeesToSponsorTokenIn + ) internal returns (address /* finalToken */, uint256 finalAmount, uint256 extraFeesToSponsorFinalToken) { // Decode the compressed action data CompressedCall[] memory compressedCalls = abi.decode(actionData, (CompressedCall[])); // Total amount to sponsor is the extra fees to sponsor, ceiling division. - uint256 totalAmount = amount + extraFeesToSponsor; - uint256 bpsToSponsor = ((extraFeesToSponsor * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount; + uint256 totalAmount = amount + extraFeesToSponsorTokenIn; + uint256 bpsToSponsor = ((extraFeesToSponsorTokenIn * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount; uint256 maxBpsToSponsorAdjusted = maxBpsToSponsor * (10 ** (BPS_TOTAL_PRECISION - BPS_DECIMALS)); if (bpsToSponsor > maxBpsToSponsorAdjusted) { bpsToSponsor = maxBpsToSponsorAdjusted; @@ -105,8 +101,6 @@ abstract contract ArbitraryActionFlowExecutor { instructions ); - uint256 finalAmount; - // This means the swap (if one was intended) didn't happen (action failed), so we use the initial token as the final token. if (initialAmountSnapshot == IERC20(initialToken).balanceOf(address(this))) { finalToken = initialToken; @@ -124,39 +118,13 @@ abstract contract ArbitraryActionFlowExecutor { // Apply the bps to sponsor to the final amount to get the amount to sponsor, ceiling division. uint256 bpsToSponsorAdjusted = BPS_PRECISION_SCALAR - bpsToSponsor; - uint256 amountToSponsor = (((finalAmount * BPS_PRECISION_SCALAR) + bpsToSponsorAdjusted - 1) / - bpsToSponsorAdjusted) - finalAmount; - if (amountToSponsor > 0) { - DonationBox donationBox = _getDonationBox(); - if (IERC20(finalToken).balanceOf(address(donationBox)) < amountToSponsor) { - amountToSponsor = 0; - } else { - donationBox.withdraw(IERC20(finalToken), amountToSponsor); - } - } + extraFeesToSponsorFinalToken = + (((finalAmount * BPS_PRECISION_SCALAR) + bpsToSponsorAdjusted - 1) / bpsToSponsorAdjusted) - + finalAmount; emit ArbitraryActionsExecuted(quoteNonce, callCount, finalAmount); - // Route to appropriate destination based on transferToCore flag - if (transferToCore) { - _executeSimpleTransferFlow( - finalAmount, - quoteNonce, - maxBpsToSponsor, - finalRecipient, - amountToSponsor, - finalToken - ); - } else { - _fallbackHyperEVMFlow( - finalAmount, - quoteNonce, - maxBpsToSponsor, - finalRecipient, - amountToSponsor, - finalToken - ); - } + return (finalToken, finalAmount, extraFeesToSponsorFinalToken); } /** @@ -202,38 +170,6 @@ abstract contract ArbitraryActionFlowExecutor { return abi.encode(instructions); } - /** - * @notice Execute simple transfer flow to HyperCore with the final token - * @dev Must be implemented by contracts that inherit from this contract - */ - function _executeSimpleTransferFlow( - uint256 finalAmount, - bytes32 quoteNonce, - uint256 maxBpsToSponsor, - address finalRecipient, - uint256 extraFeesToSponsor, - address finalToken - ) internal virtual; - - /** - * @notice Execute fallback HyperEVM flow (stay on HyperEVM) - * @dev Must be implemented by contracts that inherit from this contract - */ - function _fallbackHyperEVMFlow( - uint256 finalAmount, - bytes32 quoteNonce, - uint256 maxBpsToSponsor, - address finalRecipient, - uint256 extraFeesToSponsor, - address finalToken - ) internal virtual; - - /** - * @notice Get the donation box instance - * @dev Must be implemented by contracts that inherit from this contract - */ - function _getDonationBox() internal view virtual returns (DonationBox); - /// @notice Allow contract to receive native tokens for arbitrary action execution receive() external payable virtual {} } diff --git a/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol b/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol index 8eacf0583..3309ed3dc 100644 --- a/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol +++ b/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol @@ -9,9 +9,9 @@ import { SponsoredCCTPInterface } from "../../../interfaces/SponsoredCCTPInterfa import { Bytes32ToAddress } from "../../../libraries/AddressConverters.sol"; import { DonationBox } from "../../../chain-adapters/DonationBox.sol"; import { HyperCoreFlowExecutor } from "../HyperCoreFlowExecutor.sol"; -import { ArbitraryActionFlowExecutor } from "../ArbitraryActionFlowExecutor.sol"; +import { ArbitraryEVMFlowExecutor } from "../ArbitraryEVMFlowExecutor.sol"; -contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecutor, ArbitraryActionFlowExecutor { +contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecutor, ArbitraryEVMFlowExecutor { using SafeERC20 for IERC20Metadata; using Bytes32ToAddress for bytes32; @@ -46,7 +46,7 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu _accountActivationFeeCore, _bridgeSafetyBufferCore ) - ArbitraryActionFlowExecutor(_multicallHandler) + ArbitraryEVMFlowExecutor(_multicallHandler) { cctpMessageTransmitter = IMessageTransmitterV2(_cctpMessageTransmitter); signer = _signer; @@ -85,17 +85,17 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu (quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToCore) || quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToEVM)) ) { - // Execute arbitrary actions flow - _executeArbitraryActionFlow( + // Execute flow with arbitrary evm actions + _executeWithEVMFlow( amountAfterFees, quote.nonce, quote.maxBpsToSponsor, baseToken, // initialToken - quote.finalRecipient.toAddress(), quote.finalToken.toAddress(), + quote.finalRecipient.toAddress(), quote.actionData, - quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToCore), - feeExecuted + feeExecuted, + quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToCore) ); } else { // Execute standard HyperCore flow (default) @@ -133,45 +133,37 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu quote.deadline + quoteDeadlineBuffer >= block.timestamp; } - /// @notice Override to resolve diamond inheritance - use HyperCoreFlowExecutor implementation - function _executeSimpleTransferFlow( - uint256 finalAmount, + function _executeWithEVMFlow( + uint256 amount, bytes32 quoteNonce, uint256 maxBpsToSponsor, + address initialToken, + address finalToken, address finalRecipient, + bytes memory actionData, uint256 extraFeesToSponsor, - address finalToken - ) internal override(ArbitraryActionFlowExecutor, HyperCoreFlowExecutor) { - HyperCoreFlowExecutor._executeSimpleTransferFlow( - finalAmount, + bool transferToCore + ) internal { + uint256 finalAmount; + uint256 extraFeesToSponsorFinalToken; + (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeArbitraryActionFlow( + amount, quoteNonce, maxBpsToSponsor, - finalRecipient, - extraFeesToSponsor, - finalToken + initialToken, + finalToken, + actionData, + extraFeesToSponsor ); - } - /// @notice Override to resolve diamond inheritance - use HyperCoreFlowExecutor implementation - function _fallbackHyperEVMFlow( - uint256 finalAmount, - bytes32 quoteNonce, - uint256 maxBpsToSponsor, - address finalRecipient, - uint256 extraFeesToSponsor, - address finalToken - ) internal override(ArbitraryActionFlowExecutor, HyperCoreFlowExecutor) { - HyperCoreFlowExecutor._fallbackHyperEVMFlow( + // Route to appropriate destination based on transferToCore flag + (transferToCore ? _executeSimpleTransferFlow : _fallbackHyperEVMFlow)( finalAmount, quoteNonce, maxBpsToSponsor, finalRecipient, - extraFeesToSponsor, + extraFeesToSponsorFinalToken, finalToken ); } - - function _getDonationBox() internal view override(ArbitraryActionFlowExecutor) returns (DonationBox) { - return donationBox; - } } diff --git a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol index cf7046046..9ad08d5d9 100644 --- a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol +++ b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol @@ -10,7 +10,7 @@ import { ExecutionMode } from "./Structs.sol"; import { AddressToBytes32, Bytes32ToAddress } from "../../../libraries/AddressConverters.sol"; import { IOFT, IOAppCore } from "../../../interfaces/IOFT.sol"; import { HyperCoreFlowExecutor } from "../HyperCoreFlowExecutor.sol"; -import { ArbitraryActionFlowExecutor } from "../ArbitraryActionFlowExecutor.sol"; +import { ArbitraryEVMFlowExecutor } from "../ArbitraryEVMFlowExecutor.sol"; import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -18,7 +18,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s /// @notice Handler that receives funds from LZ system, checks authorizations(both against LZ system and src chain /// sender), and forwards authorized params to the `_executeFlow` function -contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryActionFlowExecutor { +contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryEVMFlowExecutor { using ComposeMsgCodec for bytes; using Bytes32ToAddress for bytes32; using AddressToBytes32 for address; @@ -78,7 +78,7 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryAc _accountActivationFeeCore, _bridgeSafetyBufferCore ) - ArbitraryActionFlowExecutor(_multicallHandler) + ArbitraryEVMFlowExecutor(_multicallHandler) { // baseToken is assigned on `HyperCoreFlowExecutor` creation if (baseToken != IOFT(_ioft).token()) { @@ -141,17 +141,17 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryAc executionMode == uint8(ExecutionMode.ArbitraryActionsToCore) || executionMode == uint8(ExecutionMode.ArbitraryActionsToEVM) ) { - // Execute arbitrary actions flow - _executeArbitraryActionFlow( + // Execute flow with arbitrary evm actions + _executeWithEVMFlow( amountLD, quoteNonce, maxBpsToSponsor, baseToken, // initialToken - finalRecipient, finalToken, + finalRecipient, actionData, - executionMode == uint8(ExecutionMode.ArbitraryActionsToCore), - EXTRA_FEES_TO_SPONSOR + EXTRA_FEES_TO_SPONSOR, + executionMode == uint8(ExecutionMode.ArbitraryActionsToCore) ); } else { // Execute standard HyperCore flow (default) @@ -167,6 +167,40 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryAc } } + function _executeWithEVMFlow( + uint256 amount, + bytes32 quoteNonce, + uint256 maxBpsToSponsor, + address initialToken, + address finalToken, + address finalRecipient, + bytes memory actionData, + uint256 extraFeesToSponsor, + bool transferToCore + ) internal { + uint256 finalAmount; + uint256 extraFeesToSponsorFinalToken; + (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeArbitraryActionFlow( + amount, + quoteNonce, + maxBpsToSponsor, + initialToken, + finalToken, + actionData, + extraFeesToSponsor + ); + + // Route to appropriate destination based on transferToCore flag + (transferToCore ? _executeSimpleTransferFlow : _fallbackHyperEVMFlow)( + finalAmount, + quoteNonce, + maxBpsToSponsor, + finalRecipient, + extraFeesToSponsorFinalToken, + finalToken + ); + } + /// @notice Checks that message was authorized by LayerZero's identity system and that it came from authorized src periphery function _requireAuthorizedMessage(address _oApp, bytes calldata _message) internal view { if (_oApp != IOFT_ADDRESS) { @@ -195,46 +229,4 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryAc revert UnauthorizedSrcPeriphery(_srcEid); } } - - /// @notice Override to resolve diamond inheritance - use HyperCoreFlowExecutor implementation - function _executeSimpleTransferFlow( - uint256 finalAmount, - bytes32 quoteNonce, - uint256 maxBpsToSponsor, - address finalRecipient, - uint256 extraFeesToSponsor, - address finalToken - ) internal override(ArbitraryActionFlowExecutor, HyperCoreFlowExecutor) { - HyperCoreFlowExecutor._executeSimpleTransferFlow( - finalAmount, - quoteNonce, - maxBpsToSponsor, - finalRecipient, - extraFeesToSponsor, - finalToken - ); - } - - /// @notice Override to resolve diamond inheritance - use HyperCoreFlowExecutor implementation - function _fallbackHyperEVMFlow( - uint256 finalAmount, - bytes32 quoteNonce, - uint256 maxBpsToSponsor, - address finalRecipient, - uint256 extraFeesToSponsor, - address finalToken - ) internal override(ArbitraryActionFlowExecutor, HyperCoreFlowExecutor) { - HyperCoreFlowExecutor._fallbackHyperEVMFlow( - finalAmount, - quoteNonce, - maxBpsToSponsor, - finalRecipient, - extraFeesToSponsor, - finalToken - ); - } - - function _getDonationBox() internal view override returns (DonationBox) { - return donationBox; - } } From ae16e5002864694d5f15a3757d2def93bc9d599c Mon Sep 17 00:00:00 2001 From: Ihor Farion Date: Tue, 21 Oct 2025 17:15:17 -0700 Subject: [PATCH 2/6] a few renames Signed-off-by: Ihor Farion --- contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol | 2 +- .../mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol | 4 ++-- contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol index e7aa76d20..d7d64d376 100644 --- a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol +++ b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol @@ -55,7 +55,7 @@ abstract contract ArbitraryEVMFlowExecutor { * @param actionData Encoded actions: abi.encode(CompressedCall[] calls) * @param extraFeesToSponsorTokenIn Extra fees to sponsor in initialToken */ - function _executeArbitraryActionFlow( + function _executeFlow( uint256 amount, bytes32 quoteNonce, uint256 maxBpsToSponsor, diff --git a/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol b/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol index 3309ed3dc..880a4f88c 100644 --- a/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol +++ b/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol @@ -99,7 +99,7 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu ); } else { // Execute standard HyperCore flow (default) - _executeFlow( + HyperCoreFlowExecutor._executeFlow( amountAfterFees, quote.nonce, // If the quote is invalid we don't sponsor the flow or the extra fees @@ -146,7 +146,7 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu ) internal { uint256 finalAmount; uint256 extraFeesToSponsorFinalToken; - (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeArbitraryActionFlow( + (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeFlow( amount, quoteNonce, maxBpsToSponsor, diff --git a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol index 9ad08d5d9..ed1d5a17e 100644 --- a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol +++ b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol @@ -155,7 +155,7 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryEV ); } else { // Execute standard HyperCore flow (default) - _executeFlow( + HyperCoreFlowExecutor._executeFlow( amountLD, quoteNonce, maxBpsToSponsor, @@ -180,7 +180,7 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryEV ) internal { uint256 finalAmount; uint256 extraFeesToSponsorFinalToken; - (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeArbitraryActionFlow( + (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeFlow( amount, quoteNonce, maxBpsToSponsor, From 626bd0a4d8edff6b934dc0ddae2bb233b21130b8 Mon Sep 17 00:00:00 2001 From: Ihor Farion Date: Tue, 21 Oct 2025 17:24:30 -0700 Subject: [PATCH 3/6] remove dedundant maxBpsToSponsor enforcement Signed-off-by: Ihor Farion --- contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol | 6 ------ .../mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol | 1 - .../periphery/mintburn/sponsored-oft/DstOFTHandler.sol | 1 - 3 files changed, 8 deletions(-) diff --git a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol index d7d64d376..1805eb9cb 100644 --- a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol +++ b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol @@ -49,7 +49,6 @@ abstract contract ArbitraryEVMFlowExecutor { * @dev Decompresses CompressedCall[] to MulticallHandler.Call[] format (adds value: 0) * @param amount Amount of tokens to transfer to MulticallHandler * @param quoteNonce Unique nonce for this quote - * @param maxBpsToSponsor Maximum basis points to sponsor * @param initialToken Token to transfer to MulticallHandler * @param finalToken Expected final token after actions * @param actionData Encoded actions: abi.encode(CompressedCall[] calls) @@ -58,7 +57,6 @@ abstract contract ArbitraryEVMFlowExecutor { function _executeFlow( uint256 amount, bytes32 quoteNonce, - uint256 maxBpsToSponsor, address initialToken, address finalToken, bytes memory actionData, @@ -70,10 +68,6 @@ abstract contract ArbitraryEVMFlowExecutor { // Total amount to sponsor is the extra fees to sponsor, ceiling division. uint256 totalAmount = amount + extraFeesToSponsorTokenIn; uint256 bpsToSponsor = ((extraFeesToSponsorTokenIn * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount; - uint256 maxBpsToSponsorAdjusted = maxBpsToSponsor * (10 ** (BPS_TOTAL_PRECISION - BPS_DECIMALS)); - if (bpsToSponsor > maxBpsToSponsorAdjusted) { - bpsToSponsor = maxBpsToSponsorAdjusted; - } // Snapshot balances uint256 initialAmountSnapshot = IERC20(initialToken).balanceOf(address(this)); diff --git a/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol b/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol index 880a4f88c..8e4bb19ac 100644 --- a/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol +++ b/contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol @@ -149,7 +149,6 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeFlow( amount, quoteNonce, - maxBpsToSponsor, initialToken, finalToken, actionData, diff --git a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol index ed1d5a17e..f76651244 100644 --- a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol +++ b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol @@ -183,7 +183,6 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryEV (finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeFlow( amount, quoteNonce, - maxBpsToSponsor, initialToken, finalToken, actionData, From 4ae4e77e62800747070573af178c0d1bc4d6a594 Mon Sep 17 00:00:00 2001 From: Ihor Farion Date: Tue, 21 Oct 2025 23:19:01 -0700 Subject: [PATCH 4/6] save 1 stack depth. Signed-off-by: Ihor Farion --- .../mintburn/ArbitraryEVMFlowExecutor.sol | 24 ++++++++----------- .../mintburn/sponsored-oft/DstOFTHandler.sol | 5 +--- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol index 1805eb9cb..c427589d7 100644 --- a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol +++ b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol @@ -8,14 +8,11 @@ import { DonationBox } from "../../chain-adapters/DonationBox.sol"; // Import MulticallHandler import { MulticallHandler } from "../../handlers/MulticallHandler.sol"; -// Import constants -import { BPS_SCALAR } from "./Constants.sol"; - /** * @title ArbitraryEVMFlowExecutor * @notice Base contract for executing arbitrary action sequences using MulticallHandler * @dev This contract provides shared functionality for both OFT and CCTP handlers to execute - * arbitrary actions on HyperEVM via MulticallHandler, with optional transfer to HyperCore. + * arbitrary actions on HyperEVM via MulticallHandler, returning information about the resulting token amount * @custom:security-contact bugs@across.to */ abstract contract ArbitraryEVMFlowExecutor { @@ -36,9 +33,9 @@ abstract contract ArbitraryEVMFlowExecutor { /// @notice Error thrown when final balance is insufficient error InsufficientFinalBalance(address token, uint256 expected, uint256 actual); - uint256 constant BPS_TOTAL_PRECISION = 18; - uint256 constant BPS_DECIMALS = 4; - uint256 constant BPS_PRECISION_SCALAR = 10 ** BPS_TOTAL_PRECISION; + uint256 private constant BPS_TOTAL_PRECISION = 18; + uint256 private constant BPS_DECIMALS = 4; + uint256 private constant BPS_PRECISION_SCALAR = 10 ** BPS_TOTAL_PRECISION; constructor(address _multicallHandler) { multicallHandler = _multicallHandler; @@ -66,8 +63,11 @@ abstract contract ArbitraryEVMFlowExecutor { CompressedCall[] memory compressedCalls = abi.decode(actionData, (CompressedCall[])); // Total amount to sponsor is the extra fees to sponsor, ceiling division. - uint256 totalAmount = amount + extraFeesToSponsorTokenIn; - uint256 bpsToSponsor = ((extraFeesToSponsorTokenIn * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount; + uint256 bpsToSponsor; + { + uint256 totalAmount = amount + extraFeesToSponsorTokenIn; + bpsToSponsor = ((extraFeesToSponsorTokenIn * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount; + } // Snapshot balances uint256 initialAmountSnapshot = IERC20(initialToken).balanceOf(address(this)); @@ -76,10 +76,6 @@ abstract contract ArbitraryEVMFlowExecutor { // Transfer tokens to MulticallHandler IERC20(initialToken).safeTransfer(multicallHandler, amount); - // Decompress calls: add value: 0 to each call and wrap in Instructions - // We encode Instructions with calls and a drainLeftoverTokens call at the end - uint256 callCount = compressedCalls.length; - // Build instructions for MulticallHandler bytes memory instructions = _buildMulticallInstructions( compressedCalls, @@ -116,7 +112,7 @@ abstract contract ArbitraryEVMFlowExecutor { (((finalAmount * BPS_PRECISION_SCALAR) + bpsToSponsorAdjusted - 1) / bpsToSponsorAdjusted) - finalAmount; - emit ArbitraryActionsExecuted(quoteNonce, callCount, finalAmount); + emit ArbitraryActionsExecuted(quoteNonce, compressedCalls.length, finalAmount); return (finalToken, finalAmount, extraFeesToSponsorFinalToken); } diff --git a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol index f76651244..d9f03ca4e 100644 --- a/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol +++ b/contracts/periphery/mintburn/sponsored-oft/DstOFTHandler.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.23; import { ILayerZeroComposer } from "../../../external/interfaces/ILayerZeroComposer.sol"; import { OFTComposeMsgCodec } from "../../../libraries/OFTComposeMsgCodec.sol"; -import { DonationBox } from "../../../chain-adapters/DonationBox.sol"; -import { HyperCoreLib } from "../../../libraries/HyperCoreLib.sol"; import { ComposeMsgCodec } from "./ComposeMsgCodec.sol"; import { ExecutionMode } from "./Structs.sol"; import { AddressToBytes32, Bytes32ToAddress } from "../../../libraries/AddressConverters.sol"; @@ -12,7 +10,6 @@ import { IOFT, IOAppCore } from "../../../interfaces/IOFT.sol"; import { HyperCoreFlowExecutor } from "../HyperCoreFlowExecutor.sol"; import { ArbitraryEVMFlowExecutor } from "../ArbitraryEVMFlowExecutor.sol"; -import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -37,7 +34,7 @@ contract DstOFTHandler is ILayerZeroComposer, HyperCoreFlowExecutor, ArbitraryEV /// @notice A mapping used for nonce uniqueness checks. Our src periphery and LZ should have prevented this already, /// but I guess better safe than sorry - mapping(bytes32 quoteNonce => bool used) usedNonces; + mapping(bytes32 quoteNonce => bool used) public usedNonces; /// @notice Emitted when a new authorized src periphery is configured event SetAuthorizedPeriphery(uint32 srcEid, bytes32 srcPeriphery); From c0567526bb5712375d56771e44faf09001b74456 Mon Sep 17 00:00:00 2001 From: Ihor Farion Date: Tue, 21 Oct 2025 23:29:16 -0700 Subject: [PATCH 5/6] add _calcFinalExtraFees Signed-off-by: Ihor Farion --- .../mintburn/ArbitraryEVMFlowExecutor.sol | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol index c427589d7..e6d669a85 100644 --- a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol +++ b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol @@ -62,13 +62,6 @@ abstract contract ArbitraryEVMFlowExecutor { // Decode the compressed action data CompressedCall[] memory compressedCalls = abi.decode(actionData, (CompressedCall[])); - // Total amount to sponsor is the extra fees to sponsor, ceiling division. - uint256 bpsToSponsor; - { - uint256 totalAmount = amount + extraFeesToSponsorTokenIn; - bpsToSponsor = ((extraFeesToSponsorTokenIn * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount; - } - // Snapshot balances uint256 initialAmountSnapshot = IERC20(initialToken).balanceOf(address(this)); uint256 finalAmountSnapshot = IERC20(finalToken).balanceOf(address(this)); @@ -106,11 +99,7 @@ abstract contract ArbitraryEVMFlowExecutor { } } - // Apply the bps to sponsor to the final amount to get the amount to sponsor, ceiling division. - uint256 bpsToSponsorAdjusted = BPS_PRECISION_SCALAR - bpsToSponsor; - extraFeesToSponsorFinalToken = - (((finalAmount * BPS_PRECISION_SCALAR) + bpsToSponsorAdjusted - 1) / bpsToSponsorAdjusted) - - finalAmount; + extraFeesToSponsorFinalToken = _calcFinalExtraFees(amount, extraFeesToSponsorTokenIn, finalAmount); emit ArbitraryActionsExecuted(quoteNonce, compressedCalls.length, finalAmount); @@ -160,6 +149,25 @@ abstract contract ArbitraryEVMFlowExecutor { return abi.encode(instructions); } + function _calcFinalExtraFees( + uint256 amount, + uint256 extraFeesToSponsorTokenIn, + uint256 finalAmount + ) internal pure returns (uint256 extraFeesToSponsorFinalToken) { + // Total amount to sponsor is the extra fees to sponsor, ceiling division. + uint256 bpsToSponsor; + { + uint256 totalAmount = amount + extraFeesToSponsorTokenIn; + bpsToSponsor = ((extraFeesToSponsorTokenIn * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount; + } + + // Apply the bps to sponsor to the final amount to get the amount to sponsor, ceiling division. + uint256 bpsToSponsorAdjusted = BPS_PRECISION_SCALAR - bpsToSponsor; + extraFeesToSponsorFinalToken = + (((finalAmount * BPS_PRECISION_SCALAR) + bpsToSponsorAdjusted - 1) / bpsToSponsorAdjusted) - + finalAmount; + } + /// @notice Allow contract to receive native tokens for arbitrary action execution receive() external payable virtual {} } From c0e190c12e4f3c84a9ef192a9a61dea8ba623483 Mon Sep 17 00:00:00 2001 From: Ihor Farion Date: Tue, 21 Oct 2025 23:38:50 -0700 Subject: [PATCH 6/6] comment Signed-off-by: Ihor Farion --- contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol index e6d669a85..a4fd69dad 100644 --- a/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol +++ b/contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol @@ -99,7 +99,7 @@ abstract contract ArbitraryEVMFlowExecutor { } } - extraFeesToSponsorFinalToken = _calcFinalExtraFees(amount, extraFeesToSponsorTokenIn, finalAmount); + extraFeesToSponsorFinalToken = _calcExtraFeesFinal(amount, extraFeesToSponsorTokenIn, finalAmount); emit ArbitraryActionsExecuted(quoteNonce, compressedCalls.length, finalAmount); @@ -149,7 +149,8 @@ abstract contract ArbitraryEVMFlowExecutor { return abi.encode(instructions); } - function _calcFinalExtraFees( + /// @notice Calcualtes proportional fees to sponsor in finalToken, given the fees to sponsor in initial token and initial amount + function _calcExtraFeesFinal( uint256 amount, uint256 extraFeesToSponsorTokenIn, uint256 finalAmount