chore: improve test assertion and function ordering

This commit is contained in:
pedrobergamini
2025-06-23 14:38:57 -03:00
parent 7cf3f268c1
commit 80c2ef7881
3 changed files with 182 additions and 173 deletions

View File

@@ -140,161 +140,6 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
}
}
/**
* @dev Determines the actual taker amount to be filled for a Bebop order
* @notice This function handles two scenarios:
* 1. When filledTakerAmount is 0: Uses the full order amount if givenAmount is sufficient,
* otherwise returns givenAmount to partially fill the order
* 2. When filledTakerAmount > 0: Caps the fill at the minimum of filledTakerAmount and givenAmount
* to ensure we don't attempt to fill more than available
* @param givenAmount The amount of tokens available from the router for this swap
* @param orderTakerAmount The full taker amount specified in the Bebop order
* @param filledTakerAmount The requested fill amount (0 means fill entire order)
* @return actualFilledTakerAmount The amount that will actually be filled
*/
function _getActualFilledTakerAmount(
uint256 givenAmount,
uint256 orderTakerAmount,
uint256 filledTakerAmount
) internal pure returns (uint256 actualFilledTakerAmount) {
actualFilledTakerAmount = filledTakerAmount == 0
? (givenAmount >= orderTakerAmount ? orderTakerAmount : givenAmount)
: (filledTakerAmount > givenAmount ? givenAmount : filledTakerAmount);
}
/// @dev Executes a Single RFQ swap through Bebop settlement
function _executeSingleRFQ(
address tokenIn,
address tokenOut,
TransferType transferType,
uint256 givenAmount,
uint256 filledTakerAmount,
bytes memory quoteData,
bytes memory makerSignaturesData,
bool approvalNeeded
) internal virtual returns (uint256 amountOut) {
// Decode the order from quoteData
IBebopSettlement.Single memory order =
abi.decode(quoteData, (IBebopSettlement.Single));
// Decode the MakerSignature array (should contain exactly 1 signature for Single orders)
IBebopSettlement.MakerSignature[] memory signatures =
abi.decode(makerSignaturesData, (IBebopSettlement.MakerSignature[]));
// Validate that there is exactly one maker signature
if (signatures.length != 1) {
revert BebopExecutor__InvalidInput();
}
// Get the maker signature from the first and only element of the array
IBebopSettlement.MakerSignature memory sig = signatures[0];
uint256 actualFilledTakerAmount = _getActualFilledTakerAmount(
givenAmount, order.taker_amount, filledTakerAmount
);
if (tokenIn != address(0)) {
// Transfer tokens to executor
_transfer(address(this), transferType, tokenIn, givenAmount);
}
// Approve Bebop settlement to spend tokens if needed
if (approvalNeeded) {
// slither-disable-next-line unused-return
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
}
// Record balances before swap to calculate amountOut
uint256 balanceBefore = tokenOut == address(0)
? order.receiver.balance
: IERC20(tokenOut).balanceOf(order.receiver);
// Execute the swap with ETH value if needed
uint256 ethValue = tokenIn == address(0) ? actualFilledTakerAmount : 0;
// Use swapSingle since tokens are in the executor with approval
// slither-disable-next-line arbitrary-send-eth
IBebopSettlement(bebopSettlement).swapSingle{value: ethValue}(
order, sig, actualFilledTakerAmount
);
// Calculate actual amount received
uint256 balanceAfter = tokenOut == address(0)
? order.receiver.balance
: IERC20(tokenOut).balanceOf(order.receiver);
amountOut = balanceAfter - balanceBefore;
}
/// @dev Executes an Aggregate RFQ swap through Bebop settlement
function _executeAggregateRFQ(
address tokenIn,
address tokenOut,
TransferType transferType,
uint256 givenAmount,
uint256 filledTakerAmount,
bytes memory quoteData,
bytes memory makerSignaturesData,
bool approvalNeeded
) internal virtual returns (uint256 amountOut) {
// Decode the Aggregate order
IBebopSettlement.Aggregate memory order =
abi.decode(quoteData, (IBebopSettlement.Aggregate));
// Decode the MakerSignature array (can contain multiple signatures for Aggregate orders)
IBebopSettlement.MakerSignature[] memory signatures =
abi.decode(makerSignaturesData, (IBebopSettlement.MakerSignature[]));
// Aggregate orders should have at least one signature
if (signatures.length == 0) {
revert BebopExecutor__InvalidInput();
}
// For aggregate orders, calculate total taker amount across all amounts of the 2D array
uint256 totalTakerAmount = 0;
for (uint256 i = 0; i < order.taker_amounts.length; i++) {
for (uint256 j = 0; j < order.taker_amounts[i].length; j++) {
totalTakerAmount += order.taker_amounts[i][j];
}
}
uint256 actualFilledTakerAmount = _getActualFilledTakerAmount(
givenAmount, totalTakerAmount, filledTakerAmount
);
if (tokenIn != address(0)) {
// Transfer tokens to executor
_transfer(address(this), transferType, tokenIn, givenAmount);
}
// Approve Bebop settlement to spend tokens if needed
if (approvalNeeded) {
// slither-disable-next-line unused-return
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
}
// Record balance before swap
uint256 balanceBefore = tokenOut == address(0)
? order.receiver.balance
: IERC20(tokenOut).balanceOf(order.receiver);
// Execute the swap
uint256 ethValue = tokenIn == address(0) ? actualFilledTakerAmount : 0;
// Execute the swap (tokens are in executor, approved to settlement)
// slither-disable-next-line arbitrary-send-eth
IBebopSettlement(bebopSettlement).swapAggregate{value: ethValue}(
order, signatures, actualFilledTakerAmount
);
// Calculate actual amount received
uint256 balanceAfter = tokenOut == address(0)
? order.receiver.balance
: IERC20(tokenOut).balanceOf(order.receiver);
amountOut = balanceAfter - balanceBefore;
}
/// @dev Decodes the packed calldata
function _decodeData(bytes calldata data)
internal
@@ -349,4 +194,165 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
// Extract approval flag
approvalNeeded = data[82 + quoteDataLength + makerSignaturesLength] != 0;
}
/// @dev Executes a Single RFQ swap through Bebop settlement
function _executeSingleRFQ(
address tokenIn,
address tokenOut,
TransferType transferType,
uint256 givenAmount,
uint256 filledTakerAmount,
bytes memory quoteData,
bytes memory makerSignaturesData,
bool approvalNeeded
) internal virtual returns (uint256 amountOut) {
// Decode the order from quoteData
IBebopSettlement.Single memory order =
abi.decode(quoteData, (IBebopSettlement.Single));
// Decode the MakerSignature array (should contain exactly 1 signature for Single orders)
IBebopSettlement.MakerSignature[] memory signatures =
abi.decode(makerSignaturesData, (IBebopSettlement.MakerSignature[]));
// Validate that there is exactly one maker signature
if (signatures.length != 1) {
revert BebopExecutor__InvalidInput();
}
// Get the maker signature from the first and only element of the array
IBebopSettlement.MakerSignature memory sig = signatures[0];
uint256 actualFilledTakerAmount = _getActualFilledTakerAmount(
givenAmount, order.taker_amount, filledTakerAmount
);
if (tokenIn != address(0)) {
// Transfer tokens to executor
_transfer(address(this), transferType, tokenIn, givenAmount);
}
// Approve Bebop settlement to spend tokens if needed
if (approvalNeeded) {
// slither-disable-next-line unused-return
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
}
// Record balances before swap to calculate amountOut
uint256 balanceBefore = _balanceOf(tokenOut, order.receiver);
// Execute the swap with ETH value if needed
uint256 ethValue = tokenIn == address(0) ? actualFilledTakerAmount : 0;
// Use swapSingle since tokens are in the executor with approval
// slither-disable-next-line arbitrary-send-eth
IBebopSettlement(bebopSettlement).swapSingle{value: ethValue}(
order, sig, actualFilledTakerAmount
);
// Calculate actual amount received
uint256 balanceAfter = _balanceOf(tokenOut, order.receiver);
amountOut = balanceAfter - balanceBefore;
}
/// @dev Executes an Aggregate RFQ swap through Bebop settlement
function _executeAggregateRFQ(
address tokenIn,
address tokenOut,
TransferType transferType,
uint256 givenAmount,
uint256 filledTakerAmount,
bytes memory quoteData,
bytes memory makerSignaturesData,
bool approvalNeeded
) internal virtual returns (uint256 amountOut) {
// Decode the Aggregate order
IBebopSettlement.Aggregate memory order =
abi.decode(quoteData, (IBebopSettlement.Aggregate));
// Decode the MakerSignature array (can contain multiple signatures for Aggregate orders)
IBebopSettlement.MakerSignature[] memory signatures =
abi.decode(makerSignaturesData, (IBebopSettlement.MakerSignature[]));
// Aggregate orders should have at least one signature
if (signatures.length == 0) {
revert BebopExecutor__InvalidInput();
}
// For aggregate orders, calculate total taker amount across all amounts of the 2D array
uint256 totalTakerAmount = 0;
for (uint256 i = 0; i < order.taker_amounts.length; i++) {
for (uint256 j = 0; j < order.taker_amounts[i].length; j++) {
totalTakerAmount += order.taker_amounts[i][j];
}
}
uint256 actualFilledTakerAmount = _getActualFilledTakerAmount(
givenAmount, totalTakerAmount, filledTakerAmount
);
if (tokenIn != address(0)) {
// Transfer tokens to executor
_transfer(address(this), transferType, tokenIn, givenAmount);
}
// Approve Bebop settlement to spend tokens if needed
if (approvalNeeded) {
// slither-disable-next-line unused-return
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
}
// Record balance before swap
uint256 balanceBefore = _balanceOf(tokenOut, order.receiver);
// Execute the swap
uint256 ethValue = tokenIn == address(0) ? actualFilledTakerAmount : 0;
// Execute the swap (tokens are in executor, approved to settlement)
// slither-disable-next-line arbitrary-send-eth
IBebopSettlement(bebopSettlement).swapAggregate{value: ethValue}(
order, signatures, actualFilledTakerAmount
);
// Calculate actual amount received
uint256 balanceAfter = _balanceOf(tokenOut, order.receiver);
amountOut = balanceAfter - balanceBefore;
}
/**
* @dev Determines the actual taker amount to be filled for a Bebop order
* @notice This function handles two scenarios:
* 1. When filledTakerAmount is 0: Uses the full order amount if givenAmount is sufficient,
* otherwise returns givenAmount to partially fill the order
* 2. When filledTakerAmount > 0: Caps the fill at the minimum of filledTakerAmount and givenAmount
* to ensure we don't attempt to fill more than available
* @param givenAmount The amount of tokens available from the router for this swap
* @param orderTakerAmount The full taker amount specified in the Bebop order
* @param filledTakerAmount The requested fill amount (0 means fill entire order)
* @return actualFilledTakerAmount The amount that will actually be filled
*/
function _getActualFilledTakerAmount(
uint256 givenAmount,
uint256 orderTakerAmount,
uint256 filledTakerAmount
) internal pure returns (uint256 actualFilledTakerAmount) {
actualFilledTakerAmount = filledTakerAmount == 0
? (givenAmount >= orderTakerAmount ? orderTakerAmount : givenAmount)
: (filledTakerAmount > givenAmount ? givenAmount : filledTakerAmount);
}
/// @dev Returns the balance of a token or ETH for an account
/// @param token The token address, or address(0) for ETH
/// @param account The account to get the balance of
/// @return balance The balance of the token or ETH for the account
function _balanceOf(address token, address account)
internal
view
returns (uint256)
{
return token == address(0)
? account.balance
: IERC20(token).balanceOf(account);
}
}

View File

@@ -298,9 +298,9 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
function testSingleBebopIntegration() public {
// The calldata swaps 200 USDC for ONDO
// The receiver in the order is 0xc5564C13A157E6240659fb81882A28091add8670
address orderReceiver = 0xc5564C13A157E6240659fb81882A28091add8670;
address orderTaker = 0xc5564C13A157E6240659fb81882A28091add8670;
address maker = 0xCe79b081c0c924cb67848723ed3057234d10FC6b;
deal(USDC_ADDR, ALICE, 200 * 10 ** 6); // 200 USDC
deal(USDC_ADDR, orderTaker, 200 * 10 ** 6); // 200 USDC
uint256 expAmountOut = 237212396774431060000; // Expected ONDO amount from calldata
// Fund the maker with ONDO and approve settlement
@@ -308,7 +308,9 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
vm.prank(maker);
IERC20(ONDO_ADDR).approve(BEBOP_SETTLEMENT, expAmountOut);
vm.startPrank(ALICE);
uint256 ondoBefore = IERC20(ONDO_ADDR).balanceOf(orderTaker);
vm.startPrank(orderTaker);
IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max);
// Load calldata from file
@@ -318,9 +320,10 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
(bool success,) = tychoRouterAddr.call(callData);
// Check the receiver's balance (not ALICE, since the order specifies a different receiver)
uint256 finalBalance = IERC20(ONDO_ADDR).balanceOf(orderReceiver);
uint256 ondoReceived =
IERC20(ONDO_ADDR).balanceOf(orderTaker) - ondoBefore;
assertTrue(success, "Call Failed");
assertGe(finalBalance, expAmountOut);
assertEq(ondoReceived, expAmountOut);
assertEq(
IERC20(USDC_ADDR).balanceOf(tychoRouterAddr),
0,
@@ -363,7 +366,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
uint256 finalBalance = IERC20(USDC_ADDR).balanceOf(orderTaker);
assertTrue(success, "Call Failed");
assertGe(finalBalance, expAmountOut);
assertEq(finalBalance, expAmountOut);
assertEq(address(tychoRouterAddr).balance, 0, "ETH left in router");
vm.stopPrank();

File diff suppressed because one or more lines are too long