chore: update BebopExecutor

This commit is contained in:
pedrobergamini
2025-08-04 10:25:00 -03:00
parent f9ef8e0dbd
commit 276d611157

View File

@@ -66,12 +66,9 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
using Math for uint256; using Math for uint256;
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
/// @notice Bebop order types /// @notice Function selectors for Bebop settlement methods
enum OrderType { bytes4 constant SWAP_SINGLE_SELECTOR = 0x47fb5891;
Single, // 0: Single token pair trade bytes4 constant SWAP_AGGREGATE_SELECTOR = 0x80d2cf33;
Aggregate // 1: Multi-maker trade with single token in/out
}
/// @notice Bebop-specific errors /// @notice Bebop-specific errors
error BebopExecutor__InvalidDataLength(); error BebopExecutor__InvalidDataLength();
@@ -92,11 +89,12 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
/// @notice Executes a swap through Bebop's PMM RFQ system /// @notice Executes a swap through Bebop's PMM RFQ system
/// @param givenAmount The amount of input token to swap /// @param givenAmount The amount of input token to swap
/// @param data Encoded swap data containing tokens and quote information /// @param data Encoded swap data containing tokens and bebop calldata
/// @return calculatedAmount The amount of output token received /// @return calculatedAmount The amount of output token received
function swap(uint256 givenAmount, bytes calldata data) function swap(uint256 givenAmount, bytes calldata data)
external external
payable payable
virtual
override override
returns (uint256 calculatedAmount) returns (uint256 calculatedAmount)
{ {
@@ -105,39 +103,51 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
TransferType transferType, TransferType transferType,
OrderType orderType, bytes memory bebopCalldata,
uint256 filledTakerAmount, uint256 originalAmountIn,
bytes memory quoteData,
bytes memory makerSignaturesData,
bool approvalNeeded bool approvalNeeded
) = _decodeData(data); ) = _decodeData(data);
// Execute RFQ swap based on order type // Determine if we need to modify filledTakerAmount based on slippage
if (orderType == OrderType.Single) { bytes memory finalCalldata = bebopCalldata;
calculatedAmount = _executeSingleRFQ( if (givenAmount != originalAmountIn) {
tokenIn, // Need to modify the filledTakerAmount in the calldata
tokenOut, finalCalldata = _modifyFilledTakerAmount(
transferType, bebopCalldata, givenAmount, originalAmountIn
givenAmount,
filledTakerAmount,
quoteData,
makerSignaturesData,
approvalNeeded
); );
} else if (orderType == OrderType.Aggregate) {
calculatedAmount = _executeAggregateRFQ(
tokenIn,
tokenOut,
transferType,
givenAmount,
filledTakerAmount,
quoteData,
makerSignaturesData,
approvalNeeded
);
} else {
revert BebopExecutor__InvalidInput();
} }
// Transfer tokens if needed
if (tokenIn != address(0)) {
_transfer(address(this), transferType, tokenIn, givenAmount);
}
// Approve Bebop settlement to spend tokens if needed
if (approvalNeeded && tokenIn != address(0)) {
// slither-disable-next-line unused-return
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
}
// Bebop orders specify the receiver, so we need to check the receiver's balance
// We'll use the executor's balance since Bebop should send tokens here for the router to collect
uint256 balanceBefore = _balanceOf(tokenOut, address(this));
// Execute the swap with the forwarded calldata
uint256 ethValue = tokenIn == address(0) ? givenAmount : 0;
// slither-disable-next-line arbitrary-send-eth
(bool success, bytes memory result) =
bebopSettlement.call{value: ethValue}(finalCalldata);
if (!success) {
// If the call failed, bubble up the revert reason
assembly {
revert(add(result, 0x20), mload(result))
}
}
// Calculate actual amount received by the executor
uint256 balanceAfter = _balanceOf(tokenOut, address(this));
calculatedAmount = balanceAfter - balanceBefore;
} }
/// @dev Decodes the packed calldata /// @dev Decodes the packed calldata
@@ -148,190 +158,48 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
TransferType transferType, TransferType transferType,
OrderType orderType, bytes memory bebopCalldata,
uint256 filledTakerAmount, uint256 originalAmountIn,
bytes memory quoteData,
bytes memory makerSignaturesData,
bool approvalNeeded bool approvalNeeded
) )
{ {
// Need at least 83 bytes for the minimum fixed fields // Need at least 78 bytes for the minimum fixed fields
// 20 + 20 + 1 + 1 + 32 (filledTakerAmount) + 4 (quote length) + 4 (maker sigs length) + 1 (approval) = 83 // 20 + 20 + 1 + 4 (calldata length) + 32 (original amount) + 1 (approval) = 78
if (data.length < 83) revert BebopExecutor__InvalidDataLength(); if (data.length < 78) revert BebopExecutor__InvalidDataLength();
// Decode fixed fields // Decode fixed fields
tokenIn = address(bytes20(data[0:20])); tokenIn = address(bytes20(data[0:20]));
tokenOut = address(bytes20(data[20:40])); tokenOut = address(bytes20(data[20:40]));
transferType = TransferType(uint8(data[40])); transferType = TransferType(uint8(data[40]));
orderType = OrderType(uint8(data[41]));
filledTakerAmount = uint256(bytes32(data[42:74]));
// Get quote data length and validate // Get bebop calldata length and validate
uint32 quoteDataLength = uint32(bytes4(data[74:78])); uint32 bebopCalldataLength = uint32(bytes4(data[41:45]));
if (data.length < 78 + quoteDataLength + 4) { if (data.length != 78 + bebopCalldataLength) {
revert BebopExecutor__InvalidDataLength(); revert BebopExecutor__InvalidDataLength();
} }
// Extract quote data // Extract bebop calldata
quoteData = data[78:78 + quoteDataLength]; bebopCalldata = data[45:45 + bebopCalldataLength];
// Get maker signatures data length // Extract original amount in
uint32 makerSignaturesLength = originalAmountIn = uint256(
uint32(bytes4(data[78 + quoteDataLength:82 + quoteDataLength])); bytes32(data[45 + bebopCalldataLength:77 + bebopCalldataLength])
);
// Validate total length
// 78 + quoteDataLength + 4 + makerSignaturesLength + 1 (approval)
uint256 expectedLength = 83 + quoteDataLength + makerSignaturesLength;
if (data.length != expectedLength) {
revert BebopExecutor__InvalidDataLength();
}
// Extract maker signatures data (ABI encoded MakerSignature array)
makerSignaturesData = data[
82 + quoteDataLength:82 + quoteDataLength + makerSignaturesLength
];
// Extract approval flag // Extract approval flag
approvalNeeded = data[82 + quoteDataLength + makerSignaturesLength] != 0; approvalNeeded = data[77 + bebopCalldataLength] != 0;
} }
/// @dev Executes a Single RFQ swap through Bebop settlement /// @dev Determines the actual taker amount to be filled for a Bebop order
function _executeSingleRFQ( /// @notice This function handles two scenarios:
address tokenIn, /// 1. When filledTakerAmount is 0: Uses the full order amount if given amount is sufficient,
address tokenOut, /// otherwise returns givenAmount to partially fill the order
TransferType transferType, /// 2. When filledTakerAmount > 0: Caps the fill at the minimum of filledTakerAmount
uint256 givenAmount, /// to ensure we don't attempt to fill more than available
uint256 filledTakerAmount, /// @param givenAmount The amount of tokens available from the router for this swap
bytes memory quoteData, /// @param orderTakerAmount The full taker amount specified in the Bebop order
bytes memory makerSignaturesData, /// @param filledTakerAmount The requested fill amount (0 means fill entire order)
bool approvalNeeded /// @return actualFilledTakerAmount The amount that will actually be filled
) 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( function _getActualFilledTakerAmount(
uint256 givenAmount, uint256 givenAmount,
uint256 orderTakerAmount, uint256 orderTakerAmount,
@@ -342,6 +210,76 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
: (filledTakerAmount > givenAmount ? givenAmount : filledTakerAmount); : (filledTakerAmount > givenAmount ? givenAmount : filledTakerAmount);
} }
/// @dev Modifies the filledTakerAmount in the bebop calldata to handle slippage
/// @param bebopCalldata The original calldata for the bebop settlement
/// @param givenAmount The actual amount available from the router
/// @param originalAmountIn The original amount expected when the quote was generated
/// @return modifiedCalldata The modified calldata with updated filledTakerAmount
function _modifyFilledTakerAmount(
bytes memory bebopCalldata,
uint256 givenAmount,
uint256 originalAmountIn
) internal pure returns (bytes memory) {
// Check the function selector to determine order type
bytes4 selector;
assembly {
selector := mload(add(bebopCalldata, 32))
}
if (
selector == SWAP_SINGLE_SELECTOR
|| selector == SWAP_AGGREGATE_SELECTOR
) {
// Both swapSingle and swapAggregate have filledTakerAmount as the last parameter
// For swapSingle: (Single calldata order, MakerSignature calldata makerSignature, uint256 filledTakerAmount)
// For swapAggregate: (Aggregate calldata order, MakerSignature[] calldata makerSignatures, uint256 filledTakerAmount)
// The filledTakerAmount is always the last 32 bytes of the calldata
// Calculate the new filledTakerAmount proportionally
// If originalAmountIn was X and filledTakerAmount was Y,
// then new filledTakerAmount = (Y * givenAmount) / X
uint256 calldataLength = bebopCalldata.length;
if (calldataLength < 36) revert BebopExecutor__InvalidInput(); // 4 bytes selector + at least 32 bytes
// Extract original filledTakerAmount (last 32 bytes)
uint256 originalFilledTakerAmount;
assembly {
originalFilledTakerAmount :=
mload(add(bebopCalldata, calldataLength))
}
// Calculate new filledTakerAmount using _getActualFilledTakerAmount
uint256 newFilledTakerAmount = _getActualFilledTakerAmount(
givenAmount, originalAmountIn, originalFilledTakerAmount
);
// If the new filledTakerAmount is the same as the original, return the original calldata
if (newFilledTakerAmount == originalFilledTakerAmount) {
return bebopCalldata;
}
// Create modified calldata
bytes memory modifiedCalldata = new bytes(calldataLength);
// Copy all data except the last 32 bytes
for (uint256 i = 0; i < calldataLength - 32; i++) {
modifiedCalldata[i] = bebopCalldata[i];
}
// Write the new filledTakerAmount in the last 32 bytes
assembly {
mstore(
add(modifiedCalldata, calldataLength), newFilledTakerAmount
)
}
return modifiedCalldata;
} else {
revert BebopExecutor__InvalidInput();
}
}
/// @dev Returns the balance of a token or ETH for an account /// @dev Returns the balance of a token or ETH for an account
/// @param token The token address, or address(0) for ETH /// @param token The token address, or address(0) for ETH
/// @param account The account to get the balance of /// @param account The account to get the balance of