chore: include partialFillOffset as part of user_data

This commit is contained in:
pedrobergamini
2025-08-05 20:32:42 -03:00
parent 4cb570edb1
commit 504a4db702
5 changed files with 150 additions and 65 deletions

View File

@@ -112,6 +112,7 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
address tokenOut,
TransferType transferType,
bytes memory bebopCalldata,
uint8 partialFillOffset,
uint256 originalFilledTakerAmount,
bool approvalNeeded
) = _decodeData(data);
@@ -119,7 +120,7 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
// Modify the filledTakerAmount in the calldata
// If the filledTakerAmount is the same as the original, the original calldata is returned
bytes memory finalCalldata = _modifyFilledTakerAmount(
bebopCalldata, givenAmount, originalFilledTakerAmount
bebopCalldata, givenAmount, originalFilledTakerAmount, partialFillOffset
);
// Transfer tokens if needed
@@ -157,13 +158,14 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
address tokenOut,
TransferType transferType,
bytes memory bebopCalldata,
uint8 partialFillOffset,
uint256 originalFilledTakerAmount,
bool approvalNeeded
)
{
// Need at least 78 bytes for the minimum fixed fields
// 20 + 20 + 1 + 4 (calldata length) + 32 (original amount) + 1 (approval) = 78
if (data.length < 78) revert BebopExecutor__InvalidDataLength();
// Need at least 79 bytes for the minimum fixed fields
// 20 + 20 + 1 + 4 (calldata length) + 1 (offset) + 32 (original amount) + 1 (approval) = 79
if (data.length < 79) revert BebopExecutor__InvalidDataLength();
// Decode fixed fields
tokenIn = address(bytes20(data[0:20]));
@@ -172,58 +174,44 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
// Get bebop calldata length and validate
uint32 bebopCalldataLength = uint32(bytes4(data[41:45]));
if (data.length != 78 + bebopCalldataLength) {
if (data.length != 79 + bebopCalldataLength) {
revert BebopExecutor__InvalidDataLength();
}
// Extract bebop calldata
bebopCalldata = data[45:45 + bebopCalldataLength];
// Extract partial fill offset
partialFillOffset = uint8(data[45 + bebopCalldataLength]);
// Extract original amount in
originalFilledTakerAmount = uint256(
bytes32(data[45 + bebopCalldataLength:77 + bebopCalldataLength])
bytes32(data[46 + bebopCalldataLength:78 + bebopCalldataLength])
);
// Extract approval flag
approvalNeeded = data[77 + bebopCalldataLength] != 0;
}
/// @dev Determines the actual taker amount to be filled for a Bebop order
/// @notice The encoder ensures filledTakerAmount is never 0 by extracting from order data when needed.
/// This function simply caps the fill amount at the available tokens from the router.
/// @param givenAmount The amount of tokens available from the router for this swap
/// @param filledTakerAmount The requested fill amount (guaranteed to be non-zero by encoder)
/// @return actualFilledTakerAmount The amount that will actually be filled
function _getActualFilledTakerAmount(
uint256 givenAmount,
uint256 filledTakerAmount
) internal pure returns (uint256 actualFilledTakerAmount) {
actualFilledTakerAmount =
filledTakerAmount > givenAmount ? givenAmount : filledTakerAmount;
approvalNeeded = data[78 + bebopCalldataLength] != 0;
}
/// @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 originalFilledTakerAmount The original amount expected when the quote was generated
/// @param partialFillOffset The offset from Bebop API indicating where filledTakerAmount is located
/// @return The modified calldata with updated filledTakerAmount
function _modifyFilledTakerAmount(
bytes memory bebopCalldata,
uint256 givenAmount,
uint256 originalFilledTakerAmount
uint256 originalFilledTakerAmount,
uint8 partialFillOffset
) public pure returns (bytes memory) {
bytes4 selector = _getSelector(bebopCalldata);
// Use the offset from Bebop API to locate filledTakerAmount
// Position = 4 bytes (selector) + offset * 32 bytes
uint256 filledTakerAmountPos = 4 + uint256(partialFillOffset) * 32;
// The position of filledTakerAmount differs between swapSingle and swapAggregate
// due to how Solidity encodes structs:
// - swapSingle: Single struct is encoded inline (no offset), so filledTakerAmount is at position 388
// - swapAggregate: Aggregate struct uses offset (has arrays), so filledTakerAmount is at position 68
uint256 filledTakerAmountPos =
selector == SWAP_SINGLE_SELECTOR ? 388 : 68;
// Calculate new filledTakerAmount using _getActualFilledTakerAmount
// Cap the fill amount at what we actually have available
uint256 newFilledTakerAmount =
_getActualFilledTakerAmount(givenAmount, originalFilledTakerAmount);
originalFilledTakerAmount > givenAmount ? givenAmount : originalFilledTakerAmount;
// If the new filledTakerAmount is the same as the original, return the original calldata
if (newFilledTakerAmount == originalFilledTakerAmount) {

File diff suppressed because one or more lines are too long

View File

@@ -100,6 +100,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(12), // partialFillOffset for swapSingle (388 = 4 + 12*32)
originalAmountIn,
uint8(1) // approvalNeeded: true
);
@@ -110,6 +111,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
address tokenOut,
RestrictTransferFrom.TransferType transferType,
bytes memory decodedBebopCalldata,
uint8 decodedPartialFillOffset,
uint256 decodedOriginalAmountIn,
bool decodedApprovalNeeded
) = bebopExecutor.decodeParams(params);
@@ -126,6 +128,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
keccak256(bebopCalldata),
"bebopCalldata mismatch"
);
assertEq(decodedPartialFillOffset, 12, "partialFillOffset mismatch");
assertEq(
decodedOriginalAmountIn,
originalAmountIn,
@@ -212,6 +215,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(12), // partialFillOffset for swapSingle (388 = 4 + 12*32)
testData.order.taker_amount, // originalAmountIn (matches what encoder would produce)
uint8(1) // approvalNeeded: true
);
@@ -307,6 +311,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(12), // partialFillOffset for swapSingle (388 = 4 + 12*32)
testData.order.taker_amount, // originalAmountIn (full order amount)
uint8(1) // approvalNeeded: true
);
@@ -441,6 +446,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(2), // partialFillOffset for swapAggregate (68 = 4 + 2*32)
totalTakerAmount, // originalAmountIn
uint8(0) // approvalNeeded: false for native ETH
);
@@ -580,6 +586,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(2), // partialFillOffset for swapAggregate (68 = 4 + 2*32)
totalTakerAmount, // originalAmountIn (full order amount)
uint8(0) // approvalNeeded: false for native ETH
);
@@ -628,6 +635,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(12), // partialFillOffset for swapSingle (388 = 4 + 12*32)
originalAmountIn,
uint8(1) // approvalNeeded: true
);
@@ -703,6 +711,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(12), // partialFillOffset for swapSingle (388 = 4 + 12*32)
uint256(200000000), // originalAmountIn
uint8(1) // approvalNeeded: true
);
@@ -823,6 +832,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer),
uint32(bebopCalldata.length),
bebopCalldata,
uint8(2), // partialFillOffset for swapAggregate (68 = 4 + 2*32)
ethAmount, // originalAmountIn
uint8(0) // approvalNeeded: false for native ETH
);
@@ -1098,7 +1108,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
bytes memory modifiedCalldata = bebopExecutor
.exposed_modifyFilledTakerAmount(
originalCalldata, givenAmount, originalAmountIn
originalCalldata, givenAmount, originalAmountIn, 12 // partialFillOffset for swapSingle
);
// Decode the modified calldata to verify the filledTakerAmount was updated
@@ -1174,7 +1184,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
bytes memory modifiedCalldata = bebopExecutor
.exposed_modifyFilledTakerAmount(
originalCalldata, givenAmount, originalAmountIn
originalCalldata, givenAmount, originalAmountIn, 2 // partialFillOffset for swapAggregate
);
// Decode the modified calldata to verify the filledTakerAmount was updated
@@ -1233,7 +1243,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
// So we'll test that it properly sets the value we want
bytes memory modifiedCalldata = bebopExecutor
.exposed_modifyFilledTakerAmount(
originalCalldata, givenAmount, originalAmountIn
originalCalldata, givenAmount, originalAmountIn, 12 // partialFillOffset for swapSingle
);
// Extract the new filledTakerAmount
@@ -1249,7 +1259,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
// Normal test - amounts match so calldata should be unchanged
bytes memory modifiedCalldata = bebopExecutor
.exposed_modifyFilledTakerAmount(
originalCalldata, givenAmount, originalAmountIn
originalCalldata, givenAmount, originalAmountIn, 12 // partialFillOffset for swapSingle
);
assertEq(

View File

@@ -3,9 +3,11 @@ pragma solidity ^0.8.10;
import "../../src/executors/BebopExecutor.sol";
import {Test, console} from "forge-std/Test.sol";
import "@openzeppelin/contracts/utils/Address.sol";
contract BebopExecutorHarness is BebopExecutor, Test {
using SafeERC20 for IERC20;
using Address for address;
constructor(address _bebopSettlement, address _permit2)
BebopExecutor(_bebopSettlement, _permit2)
@@ -56,6 +58,7 @@ contract BebopExecutorHarness is BebopExecutor, Test {
address tokenOut,
TransferType transferType,
bytes memory bebopCalldata,
uint8 partialFillOffset,
uint256 originalFilledTakerAmount,
bool approvalNeeded
)
@@ -63,22 +66,24 @@ contract BebopExecutorHarness is BebopExecutor, Test {
return _decodeData(data);
}
// Expose the internal getActualFilledTakerAmount function for testing
// No longer needed since we inlined the logic
function exposed_getActualFilledTakerAmount(
uint256 givenAmount,
uint256 filledTakerAmount
) external pure returns (uint256 actualFilledTakerAmount) {
return _getActualFilledTakerAmount(givenAmount, filledTakerAmount);
// Inline the simple logic here for backward compatibility
actualFilledTakerAmount = filledTakerAmount > givenAmount ? givenAmount : filledTakerAmount;
}
// Expose the internal modifyFilledTakerAmount function for testing
function exposed_modifyFilledTakerAmount(
bytes memory bebopCalldata,
uint256 givenAmount,
uint256 originalFilledTakerAmount
uint256 originalFilledTakerAmount,
uint8 partialFillOffset
) external pure returns (bytes memory) {
return _modifyFilledTakerAmount(
bebopCalldata, givenAmount, originalFilledTakerAmount
bebopCalldata, givenAmount, originalFilledTakerAmount, partialFillOffset
);
}
@@ -95,6 +100,7 @@ contract BebopExecutorHarness is BebopExecutor, Test {
,
TransferType transferType,
bytes memory bebopCalldata,
, // partialFillOffset not needed in test harness
uint256 originalFilledTakerAmount,
) = _decodeData(data);
@@ -133,8 +139,9 @@ contract BebopExecutorHarness is BebopExecutor, Test {
expiry = order.expiry;
}
// Inline the simple logic since _getActualFilledTakerAmount was removed
uint256 actualFilledTakerAmount =
_getActualFilledTakerAmount(givenAmount, originalFilledTakerAmount);
originalFilledTakerAmount > givenAmount ? givenAmount : originalFilledTakerAmount;
// For testing: transfer tokens from executor to taker address
// This simulates the taker having the tokens with approval
@@ -164,6 +171,7 @@ contract BebopExecutorHarness is BebopExecutor, Test {
uint256 currentTimestamp = block.timestamp;
vm.warp(expiry - 1); // Set timestamp to just before expiry
// Call the parent's internal _swap function
calculatedAmount = _swap(givenAmount, data);
// Restore original timestamp