Skip to content

Commit e5da749

Browse files
authored
fix: arbitrary actions flow (#1149)
* fix Signed-off-by: Ihor Farion <ihor@umaproject.org> * a few renames Signed-off-by: Ihor Farion <ihor@umaproject.org> * remove dedundant maxBpsToSponsor enforcement Signed-off-by: Ihor Farion <ihor@umaproject.org> * save 1 stack depth. Signed-off-by: Ihor Farion <ihor@umaproject.org> * add _calcFinalExtraFees Signed-off-by: Ihor Farion <ihor@umaproject.org> * comment Signed-off-by: Ihor Farion <ihor@umaproject.org> --------- Signed-off-by: Ihor Farion <ihor@umaproject.org>
1 parent 4e8e1a2 commit e5da749

File tree

3 files changed

+102
-186
lines changed

3 files changed

+102
-186
lines changed

contracts/periphery/mintburn/ArbitraryActionFlowExecutor.sol renamed to contracts/periphery/mintburn/ArbitraryEVMFlowExecutor.sol

Lines changed: 32 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ import { DonationBox } from "../../chain-adapters/DonationBox.sol";
88
// Import MulticallHandler
99
import { MulticallHandler } from "../../handlers/MulticallHandler.sol";
1010

11-
// Import constants
12-
import { BPS_SCALAR } from "./Constants.sol";
13-
1411
/**
15-
* @title ArbitraryActionFlowExecutor
12+
* @title ArbitraryEVMFlowExecutor
1613
* @notice Base contract for executing arbitrary action sequences using MulticallHandler
1714
* @dev This contract provides shared functionality for both OFT and CCTP handlers to execute
18-
* arbitrary actions on HyperEVM via MulticallHandler, with optional transfer to HyperCore.
15+
* arbitrary actions on HyperEVM via MulticallHandler, returning information about the resulting token amount
1916
* @custom:security-contact bugs@across.to
2017
*/
21-
abstract contract ArbitraryActionFlowExecutor {
18+
abstract contract ArbitraryEVMFlowExecutor {
2219
using SafeERC20 for IERC20;
2320

2421
/// @notice Compressed call struct (no value field to save gas)
@@ -36,9 +33,9 @@ abstract contract ArbitraryActionFlowExecutor {
3633
/// @notice Error thrown when final balance is insufficient
3734
error InsufficientFinalBalance(address token, uint256 expected, uint256 actual);
3835

39-
uint256 constant BPS_TOTAL_PRECISION = 18;
40-
uint256 constant BPS_DECIMALS = 4;
41-
uint256 constant BPS_PRECISION_SCALAR = 10 ** BPS_TOTAL_PRECISION;
36+
uint256 private constant BPS_TOTAL_PRECISION = 18;
37+
uint256 private constant BPS_DECIMALS = 4;
38+
uint256 private constant BPS_PRECISION_SCALAR = 10 ** BPS_TOTAL_PRECISION;
4239

4340
constructor(address _multicallHandler) {
4441
multicallHandler = _multicallHandler;
@@ -49,47 +46,29 @@ abstract contract ArbitraryActionFlowExecutor {
4946
* @dev Decompresses CompressedCall[] to MulticallHandler.Call[] format (adds value: 0)
5047
* @param amount Amount of tokens to transfer to MulticallHandler
5148
* @param quoteNonce Unique nonce for this quote
52-
* @param maxBpsToSponsor Maximum basis points to sponsor
5349
* @param initialToken Token to transfer to MulticallHandler
54-
* @param finalRecipient Final recipient address
5550
* @param finalToken Expected final token after actions
5651
* @param actionData Encoded actions: abi.encode(CompressedCall[] calls)
57-
* @param transferToCore Whether to transfer result to HyperCore
58-
* @param extraFeesToSponsor Extra fees to sponsor
52+
* @param extraFeesToSponsorTokenIn Extra fees to sponsor in initialToken
5953
*/
60-
function _executeArbitraryActionFlow(
54+
function _executeFlow(
6155
uint256 amount,
6256
bytes32 quoteNonce,
63-
uint256 maxBpsToSponsor,
6457
address initialToken,
65-
address finalRecipient,
6658
address finalToken,
6759
bytes memory actionData,
68-
bool transferToCore,
69-
uint256 extraFeesToSponsor
70-
) internal {
60+
uint256 extraFeesToSponsorTokenIn
61+
) internal returns (address /* finalToken */, uint256 finalAmount, uint256 extraFeesToSponsorFinalToken) {
7162
// Decode the compressed action data
7263
CompressedCall[] memory compressedCalls = abi.decode(actionData, (CompressedCall[]));
7364

74-
// Total amount to sponsor is the extra fees to sponsor, ceiling division.
75-
uint256 totalAmount = amount + extraFeesToSponsor;
76-
uint256 bpsToSponsor = ((extraFeesToSponsor * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount;
77-
uint256 maxBpsToSponsorAdjusted = maxBpsToSponsor * (10 ** (BPS_TOTAL_PRECISION - BPS_DECIMALS));
78-
if (bpsToSponsor > maxBpsToSponsorAdjusted) {
79-
bpsToSponsor = maxBpsToSponsorAdjusted;
80-
}
81-
8265
// Snapshot balances
8366
uint256 initialAmountSnapshot = IERC20(initialToken).balanceOf(address(this));
8467
uint256 finalAmountSnapshot = IERC20(finalToken).balanceOf(address(this));
8568

8669
// Transfer tokens to MulticallHandler
8770
IERC20(initialToken).safeTransfer(multicallHandler, amount);
8871

89-
// Decompress calls: add value: 0 to each call and wrap in Instructions
90-
// We encode Instructions with calls and a drainLeftoverTokens call at the end
91-
uint256 callCount = compressedCalls.length;
92-
9372
// Build instructions for MulticallHandler
9473
bytes memory instructions = _buildMulticallInstructions(
9574
compressedCalls,
@@ -105,8 +84,6 @@ abstract contract ArbitraryActionFlowExecutor {
10584
instructions
10685
);
10786

108-
uint256 finalAmount;
109-
11087
// This means the swap (if one was intended) didn't happen (action failed), so we use the initial token as the final token.
11188
if (initialAmountSnapshot == IERC20(initialToken).balanceOf(address(this))) {
11289
finalToken = initialToken;
@@ -122,41 +99,11 @@ abstract contract ArbitraryActionFlowExecutor {
12299
}
123100
}
124101

125-
// Apply the bps to sponsor to the final amount to get the amount to sponsor, ceiling division.
126-
uint256 bpsToSponsorAdjusted = BPS_PRECISION_SCALAR - bpsToSponsor;
127-
uint256 amountToSponsor = (((finalAmount * BPS_PRECISION_SCALAR) + bpsToSponsorAdjusted - 1) /
128-
bpsToSponsorAdjusted) - finalAmount;
129-
if (amountToSponsor > 0) {
130-
DonationBox donationBox = _getDonationBox();
131-
if (IERC20(finalToken).balanceOf(address(donationBox)) < amountToSponsor) {
132-
amountToSponsor = 0;
133-
} else {
134-
donationBox.withdraw(IERC20(finalToken), amountToSponsor);
135-
}
136-
}
102+
extraFeesToSponsorFinalToken = _calcExtraFeesFinal(amount, extraFeesToSponsorTokenIn, finalAmount);
137103

138-
emit ArbitraryActionsExecuted(quoteNonce, callCount, finalAmount);
139-
140-
// Route to appropriate destination based on transferToCore flag
141-
if (transferToCore) {
142-
_executeSimpleTransferFlow(
143-
finalAmount,
144-
quoteNonce,
145-
maxBpsToSponsor,
146-
finalRecipient,
147-
amountToSponsor,
148-
finalToken
149-
);
150-
} else {
151-
_fallbackHyperEVMFlow(
152-
finalAmount,
153-
quoteNonce,
154-
maxBpsToSponsor,
155-
finalRecipient,
156-
amountToSponsor,
157-
finalToken
158-
);
159-
}
104+
emit ArbitraryActionsExecuted(quoteNonce, compressedCalls.length, finalAmount);
105+
106+
return (finalToken, finalAmount, extraFeesToSponsorFinalToken);
160107
}
161108

162109
/**
@@ -202,37 +149,25 @@ abstract contract ArbitraryActionFlowExecutor {
202149
return abi.encode(instructions);
203150
}
204151

205-
/**
206-
* @notice Execute simple transfer flow to HyperCore with the final token
207-
* @dev Must be implemented by contracts that inherit from this contract
208-
*/
209-
function _executeSimpleTransferFlow(
210-
uint256 finalAmount,
211-
bytes32 quoteNonce,
212-
uint256 maxBpsToSponsor,
213-
address finalRecipient,
214-
uint256 extraFeesToSponsor,
215-
address finalToken
216-
) internal virtual;
217-
218-
/**
219-
* @notice Execute fallback HyperEVM flow (stay on HyperEVM)
220-
* @dev Must be implemented by contracts that inherit from this contract
221-
*/
222-
function _fallbackHyperEVMFlow(
223-
uint256 finalAmount,
224-
bytes32 quoteNonce,
225-
uint256 maxBpsToSponsor,
226-
address finalRecipient,
227-
uint256 extraFeesToSponsor,
228-
address finalToken
229-
) internal virtual;
152+
/// @notice Calcualtes proportional fees to sponsor in finalToken, given the fees to sponsor in initial token and initial amount
153+
function _calcExtraFeesFinal(
154+
uint256 amount,
155+
uint256 extraFeesToSponsorTokenIn,
156+
uint256 finalAmount
157+
) internal pure returns (uint256 extraFeesToSponsorFinalToken) {
158+
// Total amount to sponsor is the extra fees to sponsor, ceiling division.
159+
uint256 bpsToSponsor;
160+
{
161+
uint256 totalAmount = amount + extraFeesToSponsorTokenIn;
162+
bpsToSponsor = ((extraFeesToSponsorTokenIn * BPS_PRECISION_SCALAR) + totalAmount - 1) / totalAmount;
163+
}
230164

231-
/**
232-
* @notice Get the donation box instance
233-
* @dev Must be implemented by contracts that inherit from this contract
234-
*/
235-
function _getDonationBox() internal view virtual returns (DonationBox);
165+
// Apply the bps to sponsor to the final amount to get the amount to sponsor, ceiling division.
166+
uint256 bpsToSponsorAdjusted = BPS_PRECISION_SCALAR - bpsToSponsor;
167+
extraFeesToSponsorFinalToken =
168+
(((finalAmount * BPS_PRECISION_SCALAR) + bpsToSponsorAdjusted - 1) / bpsToSponsorAdjusted) -
169+
finalAmount;
170+
}
236171

237172
/// @notice Allow contract to receive native tokens for arbitrary action execution
238173
receive() external payable virtual {}

contracts/periphery/mintburn/sponsored-cctp/SponsoredCCTPDstPeriphery.sol

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { SponsoredCCTPInterface } from "../../../interfaces/SponsoredCCTPInterfa
99
import { Bytes32ToAddress } from "../../../libraries/AddressConverters.sol";
1010
import { DonationBox } from "../../../chain-adapters/DonationBox.sol";
1111
import { HyperCoreFlowExecutor } from "../HyperCoreFlowExecutor.sol";
12-
import { ArbitraryActionFlowExecutor } from "../ArbitraryActionFlowExecutor.sol";
12+
import { ArbitraryEVMFlowExecutor } from "../ArbitraryEVMFlowExecutor.sol";
1313

14-
contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecutor, ArbitraryActionFlowExecutor {
14+
contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecutor, ArbitraryEVMFlowExecutor {
1515
using SafeERC20 for IERC20Metadata;
1616
using Bytes32ToAddress for bytes32;
1717

@@ -33,7 +33,7 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu
3333
address _donationBox,
3434
address _baseToken,
3535
address _multicallHandler
36-
) HyperCoreFlowExecutor(_donationBox, _baseToken) ArbitraryActionFlowExecutor(_multicallHandler) {
36+
) HyperCoreFlowExecutor(_donationBox, _baseToken) ArbitraryEVMFlowExecutor(_multicallHandler) {
3737
cctpMessageTransmitter = IMessageTransmitterV2(_cctpMessageTransmitter);
3838
signer = _signer;
3939
}
@@ -71,21 +71,21 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu
7171
(quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToCore) ||
7272
quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToEVM))
7373
) {
74-
// Execute arbitrary actions flow
75-
_executeArbitraryActionFlow(
74+
// Execute flow with arbitrary evm actions
75+
_executeWithEVMFlow(
7676
amountAfterFees,
7777
quote.nonce,
7878
quote.maxBpsToSponsor,
7979
baseToken, // initialToken
80-
quote.finalRecipient.toAddress(),
8180
quote.finalToken.toAddress(),
81+
quote.finalRecipient.toAddress(),
8282
quote.actionData,
83-
quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToCore),
84-
feeExecuted
83+
feeExecuted,
84+
quote.executionMode == uint8(ExecutionMode.ArbitraryActionsToCore)
8585
);
8686
} else {
8787
// Execute standard HyperCore flow (default)
88-
_executeFlow(
88+
HyperCoreFlowExecutor._executeFlow(
8989
amountAfterFees,
9090
quote.nonce,
9191
// If the quote is invalid we don't sponsor the flow or the extra fees
@@ -119,45 +119,36 @@ contract SponsoredCCTPDstPeriphery is SponsoredCCTPInterface, HyperCoreFlowExecu
119119
quote.deadline + quoteDeadlineBuffer >= block.timestamp;
120120
}
121121

122-
/// @notice Override to resolve diamond inheritance - use HyperCoreFlowExecutor implementation
123-
function _executeSimpleTransferFlow(
124-
uint256 finalAmount,
122+
function _executeWithEVMFlow(
123+
uint256 amount,
125124
bytes32 quoteNonce,
126125
uint256 maxBpsToSponsor,
126+
address initialToken,
127+
address finalToken,
127128
address finalRecipient,
129+
bytes memory actionData,
128130
uint256 extraFeesToSponsor,
129-
address finalToken
130-
) internal override(ArbitraryActionFlowExecutor, HyperCoreFlowExecutor) {
131-
HyperCoreFlowExecutor._executeSimpleTransferFlow(
132-
finalAmount,
131+
bool transferToCore
132+
) internal {
133+
uint256 finalAmount;
134+
uint256 extraFeesToSponsorFinalToken;
135+
(finalToken, finalAmount, extraFeesToSponsorFinalToken) = ArbitraryEVMFlowExecutor._executeFlow(
136+
amount,
133137
quoteNonce,
134-
maxBpsToSponsor,
135-
finalRecipient,
136-
extraFeesToSponsor,
137-
finalToken
138+
initialToken,
139+
finalToken,
140+
actionData,
141+
extraFeesToSponsor
138142
);
139-
}
140143

141-
/// @notice Override to resolve diamond inheritance - use HyperCoreFlowExecutor implementation
142-
function _fallbackHyperEVMFlow(
143-
uint256 finalAmount,
144-
bytes32 quoteNonce,
145-
uint256 maxBpsToSponsor,
146-
address finalRecipient,
147-
uint256 extraFeesToSponsor,
148-
address finalToken
149-
) internal override(ArbitraryActionFlowExecutor, HyperCoreFlowExecutor) {
150-
HyperCoreFlowExecutor._fallbackHyperEVMFlow(
144+
// Route to appropriate destination based on transferToCore flag
145+
(transferToCore ? _executeSimpleTransferFlow : _fallbackHyperEVMFlow)(
151146
finalAmount,
152147
quoteNonce,
153148
maxBpsToSponsor,
154149
finalRecipient,
155-
extraFeesToSponsor,
150+
extraFeesToSponsorFinalToken,
156151
finalToken
157152
);
158153
}
159-
160-
function _getDonationBox() internal view override(ArbitraryActionFlowExecutor) returns (DonationBox) {
161-
return donationBox;
162-
}
163154
}

0 commit comments

Comments
 (0)