Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@ import { DonationBox } from "../../chain-adapters/DonationBox.sol";
// Import MulticallHandler
import { MulticallHandler } from "../../handlers/MulticallHandler.sol";

// Import constants
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.
* arbitrary actions on HyperEVM via MulticallHandler, returning information about the resulting token amount
* @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)
Expand All @@ -36,9 +33,9 @@ abstract contract ArbitraryActionFlowExecutor {
/// @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;
Expand All @@ -49,47 +46,29 @@ abstract contract ArbitraryActionFlowExecutor {
* @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 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(
function _executeFlow(
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 maxBpsToSponsorAdjusted = maxBpsToSponsor * (10 ** (BPS_TOTAL_PRECISION - BPS_DECIMALS));
if (bpsToSponsor > maxBpsToSponsorAdjusted) {
bpsToSponsor = maxBpsToSponsorAdjusted;
}

// Snapshot balances
uint256 initialAmountSnapshot = IERC20(initialToken).balanceOf(address(this));
uint256 finalAmountSnapshot = IERC20(finalToken).balanceOf(address(this));

// 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,
Expand All @@ -105,8 +84,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;
Expand All @@ -122,41 +99,11 @@ 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 = _calcExtraFeesFinal(amount, extraFeesToSponsorTokenIn, 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
);
}
emit ArbitraryActionsExecuted(quoteNonce, compressedCalls.length, finalAmount);

return (finalToken, finalAmount, extraFeesToSponsorFinalToken);
}

/**
Expand Down Expand Up @@ -202,37 +149,25 @@ 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 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
) 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;
}

/**
* @notice Get the donation box instance
* @dev Must be implemented by contracts that inherit from this contract
*/
function _getDonationBox() internal view virtual returns (DonationBox);
// 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 {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -33,7 +33,7 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu
address _donationBox,
address _baseToken,
address _multicallHandler
) HyperCoreFlowExecutor(_donationBox, _baseToken) ArbitraryActionFlowExecutor(_multicallHandler) {
) HyperCoreFlowExecutor(_donationBox, _baseToken) ArbitraryEVMFlowExecutor(_multicallHandler) {
cctpMessageTransmitter = IMessageTransmitterV2(_cctpMessageTransmitter);
signer = _signer;
}
Expand Down Expand Up @@ -71,21 +71,21 @@ 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)
_executeFlow(
HyperCoreFlowExecutor._executeFlow(
amountAfterFees,
quote.nonce,
// If the quote is invalid we don't sponsor the flow or the extra fees
Expand Down Expand Up @@ -119,45 +119,36 @@ 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._executeFlow(
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;
}
}
Loading
Loading