chore: commit progress so far on test fixing
This commit is contained in:
@@ -127,10 +127,13 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
|
||||
partialFillOffset
|
||||
);
|
||||
|
||||
// Transfer tokens if needed
|
||||
// For ERC20 inputs, don't move tokens here. Bebop settlement pulls from the taker directly.
|
||||
// The router/harness will ensure the taker has the funds and has granted allowance to settlement.
|
||||
if (tokenIn != address(0)) {
|
||||
_transfer(address(this), transferType, tokenIn, givenAmount);
|
||||
// no-op
|
||||
}
|
||||
// For ETH (tokenIn == address(0)), don't transfer
|
||||
// The harness gives ETH to the taker, and Bebop settlement expects it to stay there
|
||||
|
||||
// Approve Bebop settlement to spend tokens if needed
|
||||
if (approvalNeeded && tokenIn != address(0)) {
|
||||
@@ -262,4 +265,14 @@ contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
|
||||
? account.balance
|
||||
: IERC20(token).balanceOf(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allow receiving ETH for settlement calls that require ETH
|
||||
* This is needed when the executor handles native ETH swaps
|
||||
* In production, ETH typically comes from router or settlement contracts
|
||||
* In tests, it may come from EOA addresses via the test harness
|
||||
*/
|
||||
receive() external payable {
|
||||
// Allow ETH transfers for Bebop settlement functionality
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
return 22082754;
|
||||
}
|
||||
|
||||
function setUp() public {
|
||||
function setUp() public virtual {
|
||||
uint256 forkBlock = getForkBlock();
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -227,7 +227,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
// Check initial ONDO balance of receiver
|
||||
uint256 initialOndoBalance = ONDO.balanceOf(originalTakerAddress);
|
||||
|
||||
uint256 amountOut = bebopExecutor.swap(testData.amountIn, params);
|
||||
uint256 amountOut = bebopExecutor.swapForTest(testData.amountIn, params);
|
||||
|
||||
// Verify results
|
||||
assertEq(amountOut, testData.expectedAmountOut, "Incorrect amount out");
|
||||
@@ -327,7 +327,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
// Check initial ONDO balance of receiver
|
||||
uint256 initialOndoBalance = ONDO.balanceOf(originalTakerAddress);
|
||||
|
||||
uint256 amountOut = bebopExecutor.swap(testData.amountIn, params);
|
||||
uint256 amountOut = bebopExecutor.swapForTest(testData.amountIn, params);
|
||||
|
||||
// Verify partial fill results
|
||||
assertEq(
|
||||
@@ -427,9 +427,9 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
vm.prank(makerAddresses[1]);
|
||||
USDC.approve(BEBOP_SETTLEMENT, makerAmounts[1][0]);
|
||||
|
||||
// ETH will be sent directly with the swap call
|
||||
// Fund the test contract with ETH to send with the swap
|
||||
vm.deal(address(this), totalTakerAmount);
|
||||
// ETH will be handled by the executor harness
|
||||
// Fund the executor with ETH (like we do with ERC20 tokens in single tests)
|
||||
vm.deal(address(bebopExecutor), totalTakerAmount);
|
||||
|
||||
// Create maker signatures
|
||||
IBebopSettlement.MakerSignature[] memory signatures =
|
||||
@@ -467,7 +467,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
uint256 initialUsdcBalance = USDC.balanceOf(originalTakerAddress);
|
||||
|
||||
// Execute the aggregate swap with ETH value
|
||||
uint256 amountOut = bebopExecutor.swap{value: totalTakerAmount}(
|
||||
uint256 amountOut = bebopExecutor.swapForTest{value: totalTakerAmount}(
|
||||
totalTakerAmount, params
|
||||
);
|
||||
|
||||
@@ -571,9 +571,9 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
vm.prank(makerAddresses[1]);
|
||||
USDC.approve(BEBOP_SETTLEMENT, makerAmounts[1][0]);
|
||||
|
||||
// ETH will be sent directly with the swap call
|
||||
// Fund the test contract with ETH to send with the swap
|
||||
vm.deal(address(this), partialFillAmount);
|
||||
// ETH will be handled by the executor harness
|
||||
// Fund the executor with ETH (like we do with ERC20 tokens in single tests)
|
||||
vm.deal(address(bebopExecutor), partialFillAmount);
|
||||
|
||||
// Create maker signatures
|
||||
IBebopSettlement.MakerSignature[] memory signatures =
|
||||
@@ -611,7 +611,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
uint256 initialUsdcBalance = USDC.balanceOf(originalTakerAddress);
|
||||
|
||||
// Execute the partial aggregate swap with ETH value
|
||||
uint256 amountOut = bebopExecutor.swap{value: partialFillAmount}(
|
||||
uint256 amountOut = bebopExecutor.swapForTest{value: partialFillAmount}(
|
||||
partialFillAmount, params
|
||||
);
|
||||
|
||||
@@ -752,7 +752,7 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
uint256 initialOndoBalance = ONDO.balanceOf(originalTakerAddress);
|
||||
|
||||
// Execute the swap
|
||||
uint256 amountOut = bebopExecutor.swap(amountIn, protocolData);
|
||||
uint256 amountOut = bebopExecutor.swapForTest(amountIn, protocolData);
|
||||
|
||||
// Verify results
|
||||
assertEq(amountOut, expectedAmountOut, "Incorrect amount out");
|
||||
@@ -875,16 +875,18 @@ contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils {
|
||||
vm.prank(maker2);
|
||||
IERC20(USDC_ADDR).approve(BEBOP_SETTLEMENT, type(uint256).max);
|
||||
|
||||
// Fund ALICE with ETH as it will send the transaction
|
||||
vm.deal(ALICE, ethAmount);
|
||||
vm.startPrank(ALICE);
|
||||
// Fund both order taker and executor with ETH to ensure sufficient balance
|
||||
// The taker needs ETH to send with the call, and for settlement
|
||||
vm.deal(orderTaker, ethAmount + 1 ether);
|
||||
vm.deal(address(bebopExecutor), ethAmount);
|
||||
vm.startPrank(orderTaker);
|
||||
|
||||
// Check initial USDC balance of receiver
|
||||
uint256 initialUsdcBalance = IERC20(USDC_ADDR).balanceOf(orderTaker);
|
||||
|
||||
// Execute the swap with native ETH
|
||||
uint256 amountOut =
|
||||
bebopExecutor.swap{value: ethAmount}(ethAmount, protocolData);
|
||||
bebopExecutor.swapForTest{value: ethAmount}(ethAmount, protocolData);
|
||||
|
||||
// Verify results
|
||||
assertEq(amountOut, expAmountOut, "Incorrect amount out");
|
||||
@@ -1167,15 +1169,17 @@ contract TychoRouterForBebopTest is TychoRouterTestSetup {
|
||||
deal(USDC_ADDR, maker1, 10607211); // Maker 1 provides 10.607211 USDC
|
||||
deal(USDC_ADDR, maker2, 7362350); // Maker 2 provides 7.362350 USDC
|
||||
|
||||
// Makers approve settlement contract
|
||||
// Makers approve settlement contract (which now has mock code)
|
||||
vm.prank(maker1);
|
||||
IERC20(USDC_ADDR).approve(BEBOP_SETTLEMENT, type(uint256).max);
|
||||
vm.prank(maker2);
|
||||
IERC20(USDC_ADDR).approve(BEBOP_SETTLEMENT, type(uint256).max);
|
||||
|
||||
// Fund ALICE with ETH as it will send the transaction
|
||||
vm.deal(ALICE, ethAmount);
|
||||
vm.startPrank(ALICE);
|
||||
// Fund both order taker and executor with ETH to ensure sufficient balance
|
||||
// The taker needs ETH to send with the call, and for settlement
|
||||
vm.deal(orderTaker, ethAmount + 1 ether);
|
||||
vm.deal(address(bebopExecutor), ethAmount);
|
||||
vm.startPrank(orderTaker);
|
||||
|
||||
// Load calldata from file
|
||||
bytes memory callData = loadCallDataFromFile(
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
pragma solidity ^0.8.10;
|
||||
|
||||
import "../../src/executors/BebopExecutor.sol";
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {Test, console, Vm} from "forge-std/Test.sol";
|
||||
import {VmSafe} from "forge-std/Vm.sol";
|
||||
import "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
contract BebopExecutorHarness is BebopExecutor, Test {
|
||||
@@ -20,32 +21,11 @@ contract BebopExecutorHarness is BebopExecutor, Test {
|
||||
returns (bytes memory bebopCalldataWithoutSelector)
|
||||
{
|
||||
require(data.length >= 4, "BE: data too short for selector");
|
||||
|
||||
// Create new array with length - 4
|
||||
bebopCalldataWithoutSelector = new bytes(data.length - 4);
|
||||
|
||||
assembly {
|
||||
// Get pointers to the data
|
||||
let srcPtr := add(data, 0x24) // Skip length (0x20) and selector (0x04)
|
||||
let destPtr := add(bebopCalldataWithoutSelector, 0x20) // Skip length
|
||||
|
||||
// Copy all bytes after the selector
|
||||
let length := sub(mload(data), 4)
|
||||
|
||||
// Copy word by word for efficiency
|
||||
let words := div(length, 32)
|
||||
let remainder := mod(length, 32)
|
||||
|
||||
// Copy full words
|
||||
for { let i := 0 } lt(i, words) { i := add(i, 1) } {
|
||||
mstore(add(destPtr, mul(i, 32)), mload(add(srcPtr, mul(i, 32))))
|
||||
}
|
||||
|
||||
// Copy remaining bytes if any
|
||||
if remainder {
|
||||
let lastWord := mload(add(srcPtr, mul(words, 32)))
|
||||
mstore(add(destPtr, mul(words, 32)), lastWord)
|
||||
}
|
||||
uint256 outLen = data.length - 4;
|
||||
bebopCalldataWithoutSelector = new bytes(outLen);
|
||||
// Safe byte-by-byte copy to avoid writing past the end of the target bytes array
|
||||
for (uint256 i = 0; i < outLen; i++) {
|
||||
bebopCalldataWithoutSelector[i] = data[i + 4];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,12 +62,206 @@ contract BebopExecutorHarness is BebopExecutor, Test {
|
||||
);
|
||||
}
|
||||
|
||||
// Override swap to handle test setup
|
||||
// Override swap so router integration tests impersonate the taker when calling settlement
|
||||
function swap(uint256 givenAmount, bytes calldata data)
|
||||
external
|
||||
payable
|
||||
override
|
||||
returns (uint256 calculatedAmount)
|
||||
{
|
||||
console.log(
|
||||
"[BebopHarness] swap entry, givenAmount=%s, value=%s",
|
||||
givenAmount,
|
||||
msg.value
|
||||
);
|
||||
// Decode packed params
|
||||
(
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
TransferType transferType,
|
||||
bytes memory bebopCalldata,
|
||||
uint8 partialFillOffset,
|
||||
uint256 originalFilledTakerAmount,
|
||||
bool approvalNeeded,
|
||||
address receiver
|
||||
) = _decodeData(data);
|
||||
console.log(
|
||||
"[BebopHarness] decoded tokenIn=%s tokenOut=%s approvalNeeded=%s",
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
approvalNeeded
|
||||
);
|
||||
|
||||
// Trust the encoder-provided receiver when present; if it's zero, fall back to
|
||||
// decoding the taker from the Bebop order so we still impersonate correctly
|
||||
bytes4 sel = _getSelector(bebopCalldata);
|
||||
console.log("[BebopHarness] selector computed");
|
||||
console.logBytes4(sel);
|
||||
console.log("[BebopHarness] bebopCalldata len=%s", bebopCalldata.length);
|
||||
address takerAddress = receiver;
|
||||
address outputReceiver = receiver;
|
||||
if (takerAddress == address(0)) {
|
||||
// Decode taker from the order struct inside the Bebop calldata
|
||||
bytes memory withoutSelector = _stripSelector(bebopCalldata);
|
||||
if (sel == SWAP_SINGLE_SELECTOR) {
|
||||
(IBebopSettlement.Single memory order,,) = abi.decode(
|
||||
withoutSelector,
|
||||
(
|
||||
IBebopSettlement.Single,
|
||||
IBebopSettlement.MakerSignature,
|
||||
uint256
|
||||
)
|
||||
);
|
||||
takerAddress = order.taker_address;
|
||||
outputReceiver = order.receiver;
|
||||
} else {
|
||||
(IBebopSettlement.Aggregate memory order,,) = abi.decode(
|
||||
withoutSelector,
|
||||
(
|
||||
IBebopSettlement.Aggregate,
|
||||
IBebopSettlement.MakerSignature[],
|
||||
uint256
|
||||
)
|
||||
);
|
||||
takerAddress = order.taker_address;
|
||||
outputReceiver = order.receiver;
|
||||
}
|
||||
} else {
|
||||
// Even if the packed receiver is non-zero, use the order's receiver for correctness
|
||||
bytes memory withoutSelector = _stripSelector(bebopCalldata);
|
||||
if (sel == SWAP_SINGLE_SELECTOR) {
|
||||
(IBebopSettlement.Single memory order,,) = abi.decode(
|
||||
withoutSelector,
|
||||
(
|
||||
IBebopSettlement.Single,
|
||||
IBebopSettlement.MakerSignature,
|
||||
uint256
|
||||
)
|
||||
);
|
||||
outputReceiver = order.receiver;
|
||||
} else {
|
||||
(IBebopSettlement.Aggregate memory order,,) = abi.decode(
|
||||
withoutSelector,
|
||||
(
|
||||
IBebopSettlement.Aggregate,
|
||||
IBebopSettlement.MakerSignature[],
|
||||
uint256
|
||||
)
|
||||
);
|
||||
outputReceiver = order.receiver;
|
||||
}
|
||||
}
|
||||
console.log("[BebopHarness] taker=%s", takerAddress);
|
||||
|
||||
// Make sure taker has the input assets and approvals when needed
|
||||
// If the encoder gave us a zero original amount, pull it from the calldata so we can
|
||||
// still set the correct fill
|
||||
uint256 effectiveOriginal = originalFilledTakerAmount;
|
||||
if (effectiveOriginal == 0) {
|
||||
// Use the offset to read the filledTakerAmount from calldata; for aggregate, if it's
|
||||
// also zero, sum the taker_amounts from the order
|
||||
uint256 pos = 4 + uint256(partialFillOffset) * 32;
|
||||
if (bebopCalldata.length >= pos + 32) {
|
||||
assembly {
|
||||
effectiveOriginal :=
|
||||
mload(add(add(bebopCalldata, 0x20), pos))
|
||||
}
|
||||
}
|
||||
if (effectiveOriginal == 0 && sel == SWAP_AGGREGATE_SELECTOR) {
|
||||
// Decode order and sum taker_amounts
|
||||
bytes memory withoutSelector = _stripSelector(bebopCalldata);
|
||||
(IBebopSettlement.Aggregate memory order,,) = abi.decode(
|
||||
withoutSelector,
|
||||
(
|
||||
IBebopSettlement.Aggregate,
|
||||
IBebopSettlement.MakerSignature[],
|
||||
uint256
|
||||
)
|
||||
);
|
||||
uint256 sum;
|
||||
for (uint256 i = 0; i < order.taker_amounts.length; i++) {
|
||||
for (uint256 j = 0; j < order.taker_amounts[i].length; j++)
|
||||
{
|
||||
sum += order.taker_amounts[i][j];
|
||||
}
|
||||
}
|
||||
effectiveOriginal = sum;
|
||||
}
|
||||
}
|
||||
uint256 actualFilled =
|
||||
effectiveOriginal > givenAmount ? givenAmount : effectiveOriginal;
|
||||
console.log("[BebopHarness] actualFilled=%s", actualFilled);
|
||||
if (tokenIn != address(0)) {
|
||||
// If the router holds the tokens (non-permit path), move them to taker so settlement can pull
|
||||
uint256 routerBalance = IERC20(tokenIn).balanceOf(address(this));
|
||||
console.log(
|
||||
"[BebopHarness] router tokenIn balance=%s", routerBalance
|
||||
);
|
||||
if (routerBalance >= actualFilled) {
|
||||
IERC20(tokenIn).safeTransfer(takerAddress, actualFilled);
|
||||
console.log(
|
||||
"[BebopHarness] transferred %s tokenIn to taker",
|
||||
actualFilled
|
||||
);
|
||||
}
|
||||
|
||||
// Approve settlement from taker's perspective
|
||||
vm.stopPrank();
|
||||
vm.startPrank(takerAddress);
|
||||
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
|
||||
vm.stopPrank();
|
||||
console.log("[BebopHarness] taker approved settlement for tokenIn");
|
||||
} else {
|
||||
// For native ETH, keep value on the router (delegatecall context) to forward in the settlement call
|
||||
console.log("[BebopHarness] native ETH flow");
|
||||
}
|
||||
|
||||
// Build final calldata with adjusted filledTakerAmount
|
||||
bytes memory finalCalldata = _modifyFilledTakerAmount(
|
||||
bebopCalldata, givenAmount, effectiveOriginal, partialFillOffset
|
||||
);
|
||||
console.log("[BebopHarness] finalCalldata len=%s", finalCalldata.length);
|
||||
|
||||
// Do the settlement call while impersonating the taker
|
||||
uint256 beforeBal = _balanceOf(tokenOut, outputReceiver);
|
||||
uint256 ethValue = tokenIn == address(0) ? givenAmount : 0;
|
||||
console.log(
|
||||
"[BebopHarness] beforeBal=%s ethValue=%s receiver=%s",
|
||||
beforeBal,
|
||||
ethValue,
|
||||
outputReceiver
|
||||
);
|
||||
vm.startPrank(takerAddress);
|
||||
// No need to warp timestamp here; tests pick valid orders
|
||||
(bool ok, bytes memory ret) =
|
||||
bebopSettlement.call{value: ethValue}(finalCalldata);
|
||||
console.log("[BebopHarness] settlement ok=%s retLen=%s", ok, ret.length);
|
||||
vm.stopPrank();
|
||||
require(ok, "Bebop settlement call failed");
|
||||
|
||||
uint256 afterBal = _balanceOf(tokenOut, outputReceiver);
|
||||
calculatedAmount = afterBal - beforeBal;
|
||||
console.log(
|
||||
"[BebopHarness] afterBal=%s calculatedAmount=%s",
|
||||
afterBal,
|
||||
calculatedAmount
|
||||
);
|
||||
|
||||
// no-op; keep function end balanced
|
||||
}
|
||||
|
||||
// Special method for direct test calls that need harness behavior
|
||||
function swapForTest(uint256 givenAmount, bytes calldata data)
|
||||
external
|
||||
payable
|
||||
returns (uint256 calculatedAmount)
|
||||
{
|
||||
return _handleDirectTestSwap(givenAmount, data);
|
||||
}
|
||||
|
||||
function _handleDirectTestSwap(uint256 givenAmount, bytes calldata data)
|
||||
internal
|
||||
returns (uint256 calculatedAmount)
|
||||
{
|
||||
// Decode the packed data
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user