Files
tycho-execution/foundry/test/executors/BebopExecutor.t.sol

825 lines
29 KiB
Solidity

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "../TestUtils.sol";
import "@src/executors/BebopExecutor.sol";
import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
import {Test} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract MockToken is ERC20 {
uint8 private _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_)
ERC20(name_, symbol_)
{
_decimals = decimals_;
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
}
contract BebopExecutorExposed is BebopExecutor {
constructor(address _bebopSettlement, address _permit2)
BebopExecutor(_bebopSettlement, _permit2)
{}
function decodeParams(bytes calldata data)
external
pure
returns (
address tokenIn,
address tokenOut,
RestrictTransferFrom.TransferType transferType,
BebopExecutor.OrderType orderType,
bytes memory quoteData,
uint8 signatureType,
bytes memory signature,
bool approvalNeeded
)
{
return _decodeData(data);
}
}
/// @notice Mock Bebop settlement contract for testing
contract MockBebopSettlement is Test, Constants {
function swapSingle(
IBebopSettlement.Single calldata order,
IBebopSettlement.MakerSignature calldata, /* makerSignature */
uint256 filledTakerAmount
) external payable returns (uint256 filledMakerAmount) {
// Basic validation
require(order.expiry >= block.timestamp, "Order expired");
require(filledTakerAmount <= order.taker_amount, "Exceeds order amount");
// Mock implementation handles input tokens
if (order.taker_token == address(0)) {
// For ETH input, validate msg.value
require(msg.value >= filledTakerAmount, "Insufficient ETH sent");
} else {
// For ERC20 input, transfer from sender
IERC20(order.taker_token).transferFrom(
msg.sender, address(this), filledTakerAmount
);
}
// Calculate proportional maker amount
filledMakerAmount =
(filledTakerAmount * order.maker_amount) / order.taker_amount;
address recipient = order.receiver;
if (order.maker_token == address(0)) {
// For ETH output, send ETH directly
vm.deal(recipient, recipient.balance + filledMakerAmount);
} else {
// For ERC20 output, mint tokens to recipient
uint256 recipientBalanceBefore =
IERC20(order.maker_token).balanceOf(recipient);
deal(
order.maker_token,
recipient,
recipientBalanceBefore + filledMakerAmount
);
}
return filledMakerAmount;
}
function swapSingleFromContract(
IBebopSettlement.Single calldata order,
IBebopSettlement.MakerSignature calldata, /* makerSignature */
uint256 filledTakerAmount
) external payable returns (uint256 filledMakerAmount) {
// Basic validation
require(order.expiry >= block.timestamp, "Order expired");
require(filledTakerAmount <= order.taker_amount, "Exceeds order amount");
// For swapSingleFromContract, tokens should already be in this contract
if (order.taker_token == address(0)) {
// For ETH input, validate contract balance
require(
address(this).balance >= filledTakerAmount,
"Insufficient ETH in contract"
);
} else {
// For ERC20 input, validate contract balance
require(
IERC20(order.taker_token).balanceOf(address(this))
>= filledTakerAmount,
"Insufficient tokens in contract"
);
}
// Calculate proportional maker amount
filledMakerAmount =
(filledTakerAmount * order.maker_amount) / order.taker_amount;
address recipient = order.receiver;
if (order.maker_token == address(0)) {
// For ETH output, send ETH directly
vm.deal(recipient, recipient.balance + filledMakerAmount);
} else {
// For ERC20 output, mint tokens to recipient
uint256 recipientBalanceBefore =
IERC20(order.maker_token).balanceOf(recipient);
deal(
order.maker_token,
recipient,
recipientBalanceBefore + filledMakerAmount
);
}
return filledMakerAmount;
}
function swapMulti(
IBebopSettlement.Multi calldata order,
IBebopSettlement.MakerSignature calldata, /* makerSignature */
uint256[] calldata filledTakerAmounts
) external payable returns (uint256[] memory filledMakerAmounts) {
// Basic validation
require(order.expiry >= block.timestamp, "Order expired");
require(
order.taker_tokens.length == filledTakerAmounts.length,
"Array length mismatch"
);
filledMakerAmounts = new uint256[](order.maker_tokens.length);
// Handle each token input
uint256 totalEthRequired = 0;
for (uint256 i = 0; i < order.taker_tokens.length; i++) {
if (filledTakerAmounts[i] == 0) continue;
require(
filledTakerAmounts[i] <= order.taker_amounts[i],
"Exceeds order amount"
);
if (order.taker_tokens[i] == address(0)) {
// For ETH input, accumulate required ETH
totalEthRequired += filledTakerAmounts[i];
} else {
// For ERC20 input, transfer from sender
IERC20(order.taker_tokens[i]).transferFrom(
msg.sender, address(this), filledTakerAmounts[i]
);
}
}
// Validate ETH sent
require(msg.value >= totalEthRequired, "Insufficient ETH sent");
// Calculate and distribute maker amounts
for (uint256 i = 0; i < order.maker_tokens.length; i++) {
// For single-input, multi-output orders, use the first taker amount
uint256 takerIndex = i < order.taker_tokens.length ? i : 0;
if (
takerIndex < filledTakerAmounts.length
&& filledTakerAmounts[takerIndex] > 0
) {
// Calculate proportional maker amount based on the filled ratio
filledMakerAmounts[i] = (
filledTakerAmounts[takerIndex] * order.maker_amounts[i]
) / order.taker_amounts[takerIndex];
if (order.maker_tokens[i] == address(0)) {
// For ETH output
vm.deal(
order.receiver,
order.receiver.balance + filledMakerAmounts[i]
);
} else {
// For ERC20 output
deal(
order.maker_tokens[i],
order.receiver,
IERC20(order.maker_tokens[i]).balanceOf(order.receiver)
+ filledMakerAmounts[i]
);
}
}
}
return filledMakerAmounts;
}
function swapAggregate(
IBebopSettlement.Aggregate calldata order,
IBebopSettlement.MakerSignature[] calldata, /* makerSignatures */
uint256[] calldata filledTakerAmounts
) external payable returns (uint256[][] memory filledMakerAmounts) {
// Basic validation
require(order.expiry >= block.timestamp, "Order expired");
require(
order.taker_tokens.length == filledTakerAmounts.length,
"Array length mismatch"
);
require(
order.maker_addresses.length == order.maker_tokens.length,
"Maker array mismatch"
);
filledMakerAmounts = new uint256[][](order.maker_addresses.length);
// Handle taker tokens
uint256 totalEthRequired = 0;
for (uint256 i = 0; i < order.taker_tokens.length; i++) {
if (filledTakerAmounts[i] == 0) continue;
require(
filledTakerAmounts[i] <= order.taker_amounts[i],
"Exceeds order amount"
);
if (order.taker_tokens[i] == address(0)) {
totalEthRequired += filledTakerAmounts[i];
} else {
IERC20(order.taker_tokens[i]).transferFrom(
msg.sender, address(this), filledTakerAmounts[i]
);
}
}
require(msg.value >= totalEthRequired, "Insufficient ETH sent");
// Find the first filled taker amount
uint256 filledTakerIndex = 0;
uint256 filledAmount = 0;
for (uint256 i = 0; i < filledTakerAmounts.length; i++) {
if (filledTakerAmounts[i] > 0) {
filledTakerIndex = i;
filledAmount = filledTakerAmounts[i];
break;
}
}
require(filledAmount > 0, "No taker amount filled");
// Distribute to makers proportionally
for (uint256 i = 0; i < order.maker_addresses.length; i++) {
filledMakerAmounts[i] = new uint256[](order.maker_tokens[i].length);
for (uint256 j = 0; j < order.maker_tokens[i].length; j++) {
// Calculate proportional maker amount
filledMakerAmounts[i][j] = (
filledAmount * order.maker_amounts[i][j]
) / order.taker_amounts[filledTakerIndex];
if (order.maker_tokens[i][j] == address(0)) {
vm.deal(
order.receiver,
order.receiver.balance + filledMakerAmounts[i][j]
);
} else {
deal(
order.maker_tokens[i][j],
order.receiver,
IERC20(order.maker_tokens[i][j]).balanceOf(
order.receiver
) + filledMakerAmounts[i][j]
);
}
}
}
return filledMakerAmounts;
}
}
contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
using SafeERC20 for IERC20;
BebopExecutorExposed bebopExecutor;
MockBebopSettlement mockBebopSettlement;
MockToken WETH;
MockToken USDC;
function setUp() public {
// Deploy mock tokens
WETH = new MockToken("Wrapped Ether", "WETH", 18);
USDC = new MockToken("USD Coin", "USDC", 6);
// Deploy at expected addresses
vm.etch(WETH_ADDR, address(WETH).code);
vm.etch(USDC_ADDR, address(USDC).code);
// Update references
WETH = MockToken(WETH_ADDR);
USDC = MockToken(USDC_ADDR);
// Deploy mock contracts
mockBebopSettlement = new MockBebopSettlement();
// Deploy Bebop executor
bebopExecutor = new BebopExecutorExposed(
address(mockBebopSettlement), PERMIT2_ADDRESS
);
// Fund test accounts
WETH.mint(address(this), 100e18);
USDC.mint(address(this), 100_000e6); // Mint USDC to test contract
USDC.mint(address(mockBebopSettlement), 100_000e6);
}
// Allow test contract to receive ETH
receive() external payable {}
function testDecodeParams() public view {
bytes memory quoteData = hex"1234567890abcdef";
bytes memory signature = hex"aabbccdd";
bytes memory params = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true
);
(
address tokenIn,
address tokenOut,
RestrictTransferFrom.TransferType transferType,
BebopExecutor.OrderType orderType,
bytes memory decodedQuoteData,
uint8 decodedSignatureType,
bytes memory decodedSignature,
bool decodedApprovalNeeded
) = bebopExecutor.decodeParams(params);
assertEq(tokenIn, WETH_ADDR);
assertEq(tokenOut, USDC_ADDR);
assertEq(
uint8(transferType),
uint8(RestrictTransferFrom.TransferType.Transfer)
);
assertEq(uint8(orderType), uint8(BebopExecutor.OrderType.Single));
assertEq(keccak256(decodedQuoteData), keccak256(quoteData));
assertEq(decodedSignatureType, 1); // EIP712 signature type
assertEq(keccak256(decodedSignature), keccak256(signature));
assertTrue(decodedApprovalNeeded); // Approval needed should be true
}
function testRFQSwap() public {
uint256 amountIn = 1e18; // 1 WETH
uint256 expectedAmountOut = 3000e6; // 3000 USDC
// Create a valid Bebop order
IBebopSettlement.Single memory order = IBebopSettlement.Single({
expiry: block.timestamp + 3600,
taker_address: address(0), // Any taker
maker_address: address(mockBebopSettlement),
maker_nonce: 1,
taker_token: WETH_ADDR,
maker_token: USDC_ADDR,
taker_amount: amountIn,
maker_amount: expectedAmountOut,
receiver: address(bebopExecutor), // Output should go to executor
packed_commands: 0,
flags: 0
});
// Encode order as quote data
bytes memory quoteData = abi.encode(order);
bytes memory signature = hex"aabbccdd"; // Mock signature
bytes memory params = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true
);
// Transfer WETH to executor first
WETH.transfer(address(bebopExecutor), amountIn);
// Execute swap
uint256 executorBalanceBefore = USDC.balanceOf(address(bebopExecutor));
uint256 amountOut = bebopExecutor.swap(amountIn, params);
uint256 executorBalanceAfter = USDC.balanceOf(address(bebopExecutor));
// Check that tokens ended up in the executor for the router to collect
assertEq(amountOut, expectedAmountOut);
assertEq(executorBalanceAfter - executorBalanceBefore, amountOut);
}
function testETHInput() public {
uint256 amountIn = 1e18; // 1 ETH
uint256 expectedAmountOut = 3000e6; // 3000 USDC
// Create a valid Bebop order with ETH input
IBebopSettlement.Single memory order = IBebopSettlement.Single({
expiry: block.timestamp + 3600,
taker_address: address(0), // Any taker
maker_address: address(mockBebopSettlement),
maker_nonce: 1,
taker_token: address(0), // ETH input
maker_token: USDC_ADDR,
taker_amount: amountIn,
maker_amount: expectedAmountOut,
receiver: address(bebopExecutor), // Output should go to executor
packed_commands: 0,
flags: 0
});
// Encode order as quote data
bytes memory quoteData = abi.encode(order);
bytes memory signature = hex"aabbccdd"; // Mock signature
bytes memory params = abi.encodePacked(
address(0), // ETH input
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.None), // ETH comes via msg.value
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signature.length),
signature,
uint8(0) // approvalNeeded: false for ETH
);
// Fund test contract with ETH
vm.deal(address(this), 10e18);
uint256 executorBalanceBefore = USDC.balanceOf(address(bebopExecutor));
uint256 amountOut =
bebopExecutor.swap{value: amountIn}(amountIn, params);
uint256 executorBalanceAfter = USDC.balanceOf(address(bebopExecutor));
// Check that tokens ended up in the executor for the router to collect
assertEq(amountOut, expectedAmountOut);
assertEq(executorBalanceAfter - executorBalanceBefore, amountOut);
}
function testETHOutput() public {
uint256 amountIn = 1000e6; // 1000 USDC
uint256 expectedAmountOut = 1e18; // 1 ETH
// Create a valid Bebop order with ETH output
IBebopSettlement.Single memory order = IBebopSettlement.Single({
expiry: block.timestamp + 3600,
taker_address: address(0), // Any taker
maker_address: address(mockBebopSettlement),
maker_nonce: 1,
taker_token: USDC_ADDR,
maker_token: address(0), // ETH output
taker_amount: amountIn,
maker_amount: expectedAmountOut,
receiver: address(bebopExecutor), // Output should go to executor
packed_commands: 0,
flags: 0
});
// Encode order as quote data
bytes memory quoteData = abi.encode(order);
bytes memory signature = hex"aabbccdd"; // Mock signature
bytes memory params = abi.encodePacked(
USDC_ADDR,
address(0), // ETH output
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true for USDC
);
// Transfer USDC to executor first
USDC.transfer(address(bebopExecutor), amountIn);
uint256 executorEthBalanceBefore = address(bebopExecutor).balance;
uint256 amountOut = bebopExecutor.swap(amountIn, params);
uint256 executorEthBalanceAfter = address(bebopExecutor).balance;
// Make sure the ETH ended up in the executor for the router to collect
assertEq(amountOut, expectedAmountOut);
assertEq(executorEthBalanceAfter - executorEthBalanceBefore, amountOut);
}
function testExpiredQuote() public {
uint256 amountIn = 1e18;
uint256 expectedAmountOut = 3000e6;
// Create an order with expired timestamp
IBebopSettlement.Single memory order = IBebopSettlement.Single({
expiry: block.timestamp - 1, // Already expired
taker_address: address(0),
maker_address: address(mockBebopSettlement),
maker_nonce: 1,
taker_token: WETH_ADDR,
maker_token: USDC_ADDR,
taker_amount: amountIn,
maker_amount: expectedAmountOut,
receiver: address(bebopExecutor), // Output should go to executor
packed_commands: 0,
flags: 0
});
bytes memory quoteData = abi.encode(order);
bytes memory signature = hex"aabbccdd";
bytes memory params = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true
);
// Transfer WETH to executor
WETH.transfer(address(bebopExecutor), amountIn);
// Should revert due to expired order
vm.expectRevert("Order expired");
bebopExecutor.swap(amountIn, params);
}
function testInvalidDataLength() public {
bytes memory quoteData = hex"1234567890abcdef";
bytes memory signature = hex"aabbccdd";
// Create params with correct length first
bytes memory validParams = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true
);
// Verify valid params work
bebopExecutor.decodeParams(validParams);
// Add extra bytes at the end, this should fail
bytes memory invalidParams = abi.encodePacked(validParams, hex"ff");
vm.expectRevert(BebopExecutor.BebopExecutor__InvalidDataLength.selector);
bebopExecutor.decodeParams(invalidParams);
// Try with insufficient data, should fail
bytes memory tooShortParams = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer)
);
// Missing rest of the data
vm.expectRevert(BebopExecutor.BebopExecutor__InvalidDataLength.selector);
bebopExecutor.decodeParams(tooShortParams);
}
function testMultiRFQSwap() public {
uint256 amountIn = 1e18; // 1 WETH
uint256 expectedAmountOut = 3000e6; // 3000 USDC
// Create arrays for Multi order
address[] memory takerTokens = new address[](2);
takerTokens[0] = WETH_ADDR;
takerTokens[1] = DAI_ADDR; // Not used in this test
address[] memory makerTokens = new address[](2);
makerTokens[0] = USDC_ADDR;
makerTokens[1] = WBTC_ADDR; // Not used in this test
uint256[] memory takerAmounts = new uint256[](2);
takerAmounts[0] = amountIn;
takerAmounts[1] = 0;
uint256[] memory makerAmounts = new uint256[](2);
makerAmounts[0] = expectedAmountOut;
makerAmounts[1] = 0;
// Create a valid Bebop Multi order
IBebopSettlement.Multi memory order = IBebopSettlement.Multi({
expiry: block.timestamp + 3600,
taker_address: address(0),
maker_address: address(mockBebopSettlement),
maker_nonce: 1,
taker_tokens: takerTokens,
maker_tokens: makerTokens,
taker_amounts: takerAmounts,
maker_amounts: makerAmounts,
receiver: address(bebopExecutor),
packed_commands: 0,
flags: 0
});
// Encode order as quote data
bytes memory quoteData = abi.encode(order);
bytes memory signature = hex"aabbccdd";
bytes memory params = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(1), // OrderType.Multi
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true
);
// Transfer WETH to executor first
WETH.transfer(address(bebopExecutor), amountIn);
// Execute swap
uint256 executorBalanceBefore = USDC.balanceOf(address(bebopExecutor));
uint256 amountOut = bebopExecutor.swap(amountIn, params);
uint256 executorBalanceAfter = USDC.balanceOf(address(bebopExecutor));
// Check results
assertGt(amountOut, 0);
assertEq(amountOut, expectedAmountOut);
assertEq(executorBalanceAfter - executorBalanceBefore, amountOut);
}
function testInvalidSignatureType() public {
uint256 amountIn = 1e18;
uint256 expectedAmountOut = 3000e6;
// Create a valid order but with invalid signature type
IBebopSettlement.Single memory order = IBebopSettlement.Single({
expiry: block.timestamp + 3600,
taker_address: address(0),
maker_address: address(mockBebopSettlement),
maker_nonce: 1,
taker_token: WETH_ADDR,
maker_token: USDC_ADDR,
taker_amount: amountIn,
maker_amount: expectedAmountOut,
receiver: address(bebopExecutor),
packed_commands: 0,
flags: 0
});
bytes memory quoteData = abi.encode(order);
bytes memory signature = hex"aabbccdd";
// Test with signatureType 0 (invalid)
bytes memory params = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(0), // signatureType: 0 (invalid!)
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true
);
// Transfer WETH to executor
WETH.transfer(address(bebopExecutor), amountIn);
// Should revert with invalid signature type when executing
vm.expectRevert(
BebopExecutor.BebopExecutor__InvalidSignatureType.selector
);
bebopExecutor.swap(amountIn, params);
// Test with signatureType 4 (invalid)
params = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(0), // OrderType.Single
uint32(quoteData.length),
quoteData,
uint8(4), // signatureType: 4 (invalid!)
uint32(signature.length),
signature,
uint8(1) // approvalNeeded: true
);
// Should also revert
vm.expectRevert(
BebopExecutor.BebopExecutor__InvalidSignatureType.selector
);
bebopExecutor.swap(amountIn, params);
}
function testAggregateRFQSwap() public {
uint256 amountIn = 1e18; // 1 WETH
uint256 expectedAmountOut = 3000e6; // 3000 USDC total from 2 makers
// Create arrays for Aggregate order
address[] memory takerTokens = new address[](1);
takerTokens[0] = WETH_ADDR;
uint256[] memory takerAmounts = new uint256[](1);
takerAmounts[0] = amountIn;
address[] memory makerAddresses = new address[](2);
makerAddresses[0] = address(mockBebopSettlement);
makerAddresses[1] = makeAddr("maker2");
address[][] memory makerTokens = new address[][](2);
makerTokens[0] = new address[](1);
makerTokens[0][0] = USDC_ADDR;
makerTokens[1] = new address[](1);
makerTokens[1][0] = USDC_ADDR;
uint256[][] memory makerAmounts = new uint256[][](2);
makerAmounts[0] = new uint256[](1);
makerAmounts[0][0] = 1500e6; // First maker provides 1500 USDC
makerAmounts[1] = new uint256[](1);
makerAmounts[1][0] = 1500e6; // Second maker provides 1500 USDC
// Create a valid Bebop Aggregate order
IBebopSettlement.Aggregate memory order = IBebopSettlement.Aggregate({
expiry: block.timestamp + 3600,
taker_address: address(0),
taker_nonce: 1,
taker_tokens: takerTokens,
taker_amounts: takerAmounts,
maker_addresses: makerAddresses,
maker_tokens: makerTokens,
maker_amounts: makerAmounts,
receiver: address(bebopExecutor),
packed_commands: 0,
flags: 0
});
// Encode order as quote data
bytes memory quoteData = abi.encode(order);
// Encode multiple signatures (2 makers) - for EIP712, use concatenated 65-byte signatures
bytes memory sig1 =
hex"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001";
bytes memory sig2 =
hex"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101";
bytes memory signatures = abi.encodePacked(sig1, sig2);
bytes memory params = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
uint8(RestrictTransferFrom.TransferType.Transfer),
uint8(2), // OrderType.Aggregate
uint32(quoteData.length),
quoteData,
uint8(1), // signatureType: EIP712
uint32(signatures.length),
signatures,
uint8(1) // approvalNeeded: true
);
// Transfer WETH to executor first
WETH.transfer(address(bebopExecutor), amountIn);
// Execute swap
uint256 executorBalanceBefore = USDC.balanceOf(address(bebopExecutor));
uint256 amountOut = bebopExecutor.swap(amountIn, params);
uint256 executorBalanceAfter = USDC.balanceOf(address(bebopExecutor));
// Check results
assertGt(amountOut, 0);
assertEq(amountOut, expectedAmountOut);
assertEq(executorBalanceAfter - executorBalanceBefore, amountOut);
}
}