// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.26; import "../TestUtils.sol"; import "../TychoRouterTestSetup.sol"; import "@src/executors/BebopExecutor.sol"; import {Constants} from "../Constants.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract BebopExecutorExposed is BebopExecutor { constructor(address _bebopSettlement, address _permit2) BebopExecutor(_bebopSettlement, _permit2) {} function decodeData(bytes calldata data) external pure returns ( address tokenIn, address tokenOut, TransferType transferType, uint8 partialFillOffset, uint256 originalFilledTakerAmount, bool approvalNeeded, address receiver, bytes memory bebopCalldata ) { return _decodeData(data); } } contract BebopExecutorTest is Constants, Permit2TestHelper, TestUtils { using SafeERC20 for IERC20; BebopExecutorExposed bebopExecutor; IERC20 WETH = IERC20(WETH_ADDR); IERC20 USDC = IERC20(USDC_ADDR); IERC20 DAI = IERC20(DAI_ADDR); IERC20 WBTC = IERC20(WBTC_ADDR); IERC20 ONDO = IERC20(ONDO_ADDR); IERC20 USDT = IERC20(USDT_ADDR); function testDecodeData() public { vm.createSelectFork(vm.rpcUrl("mainnet"), 22667985); bebopExecutor = new BebopExecutorExposed(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); bytes memory bebopCalldata = abi.encodePacked( bytes4(0x4dcebcba), // swapSingle selector hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000068470140" ); uint256 originalAmountIn = 200000000; // 200 USDC bytes memory params = abi.encodePacked( USDC_ADDR, ONDO_ADDR, uint8(RestrictTransferFrom.TransferType.Transfer), uint8(2), originalAmountIn, true, address(123), bebopCalldata ); ( address tokenIn, address tokenOut, RestrictTransferFrom.TransferType transferType, uint8 decodedPartialFillOffset, uint256 decodedOriginalAmountIn, bool decodedApprovalNeeded, address decodedReceiver, bytes memory decodedBebopCalldata ) = bebopExecutor.decodeData(params); assertEq(tokenIn, USDC_ADDR, "tokenIn mismatch"); assertEq(tokenOut, ONDO_ADDR, "tokenOut mismatch"); assertEq( uint8(transferType), uint8(RestrictTransferFrom.TransferType.Transfer), "transferType mismatch" ); assertEq( keccak256(decodedBebopCalldata), keccak256(bebopCalldata), "bebopCalldata mismatch" ); assertEq(decodedPartialFillOffset, 2, "partialFillOffset mismatch"); assertEq( decodedOriginalAmountIn, originalAmountIn, "originalAmountIn mismatch" ); assertTrue(decodedApprovalNeeded, "approvalNeeded should be true"); assertEq(decodedReceiver, address(123), "receiver mismatch"); } // Single Order Tests function testSingleOrder() public { // 1 WETH -> WBTC vm.createSelectFork(vm.rpcUrl("mainnet"), 23124275); bebopExecutor = new BebopExecutorExposed(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); // Quote made manually using the BebopExecutor as the taker and receiver bytes memory bebopCalldata = hex"4dcebcba00000000000000000000000000000000000000000000000000000000689b137a0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f000000000000000000000000bee3211ab312a8d065c4fef0247448e17a8da000000000000000000000000000000000000000000000000000279ead5d9683d8a5000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000037337c0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000000000000000000000000000000000000000000000f71248bc6c123bbf12adc837470f75640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000418e9b0fb72ed9b86f7a7345026269c02b9056efcdfb67a377c7ff6c4a62a4807a7671ae759edf29aea1b2cb8efc8659e3aedac72943cd3607985a1849256358641c00000000000000000000000000000000000000000000000000000000000000"; address tokenIn = WETH_ADDR; address tokenOut = WBTC_ADDR; RestrictTransferFrom.TransferType transferType = RestrictTransferFrom.TransferType.None; uint8 partialFillOffset = 12; uint256 amountIn = 1000000000000000000; bool approvalNeeded = true; uint256 expectedAmountOut = 3617660; deal(tokenIn, address(bebopExecutor), amountIn); bytes memory params = abi.encodePacked( tokenIn, tokenOut, transferType, partialFillOffset, amountIn, approvalNeeded, address(bebopExecutor), bebopCalldata ); uint256 initialTokenOutBalance = IERC20(tokenOut).balanceOf(address(bebopExecutor)); uint256 amountOut = bebopExecutor.swap(amountIn, params); assertEq(amountOut, expectedAmountOut, "Incorrect amount out"); assertEq( IERC20(tokenOut).balanceOf(address(bebopExecutor)) - initialTokenOutBalance, expectedAmountOut, "WBTC should be at receiver" ); assertEq( IERC20(tokenIn).balanceOf(address(bebopExecutor)), 0, "WETH left in executor" ); } function testSingleOrderSellingETH() public { // 1 WETH -> WBTC vm.createSelectFork(vm.rpcUrl("mainnet"), 23124275); bebopExecutor = new BebopExecutorExposed(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); // Quote made manually using the BebopExecutor as the taker and receiver bytes memory bebopCalldata = hex"4dcebcba00000000000000000000000000000000000000000000000000000000689ca0cd0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000002a65384e77863d8e000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000003a96a10000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000000000000000000000000000000000000000000001c6d9e514c7a64e5c0e239b532e1a3ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041905d474b362c4a7c901c6a4ccb5c30670a0c602456f52761b47a0a35fc3944ec1fa224bc3bc6e8925cb15258efad2cf79e22ce9720f2302d4a1a2811c54fb4341c00000000000000000000000000000000000000000000000000000000000000"; address tokenIn = address(0); address tokenOut = WBTC_ADDR; RestrictTransferFrom.TransferType transferType = RestrictTransferFrom.TransferType.None; uint8 partialFillOffset = 12; uint256 amountIn = 1000000000000000000; bool approvalNeeded = false; uint256 expectedAmountOut = 3839649; vm.deal(address(bebopExecutor), amountIn); bytes memory params = abi.encodePacked( tokenIn, tokenOut, transferType, partialFillOffset, amountIn, approvalNeeded, address(bebopExecutor), bebopCalldata ); uint256 initialTokenOutBalance = IERC20(tokenOut).balanceOf(address(bebopExecutor)); uint256 amountOut = bebopExecutor.swap(amountIn, params); assertEq(amountOut, expectedAmountOut, "Incorrect amount out"); assertEq( IERC20(tokenOut).balanceOf(address(bebopExecutor)) - initialTokenOutBalance, expectedAmountOut, "WBTC should be at receiver" ); assertEq(address(bebopExecutor).balance, 0, "ETH left in executor"); } function testSingleOrder_PartialFill() public { // 0.5 WETH -> WBTC with a quote for 1 WETH vm.createSelectFork(vm.rpcUrl("mainnet"), 23124275); bebopExecutor = new BebopExecutorExposed(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); // Quote made manually using the BebopExecutor as the taker and receiver (the same as testSingleOrder) bytes memory bebopCalldata = hex"4dcebcba00000000000000000000000000000000000000000000000000000000689b137a0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f000000000000000000000000bee3211ab312a8d065c4fef0247448e17a8da000000000000000000000000000000000000000000000000000279ead5d9683d8a5000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000037337c0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000000000000000000000000000000000000000000000f71248bc6c123bbf12adc837470f75640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000418e9b0fb72ed9b86f7a7345026269c02b9056efcdfb67a377c7ff6c4a62a4807a7671ae759edf29aea1b2cb8efc8659e3aedac72943cd3607985a1849256358641c00000000000000000000000000000000000000000000000000000000000000"; address tokenIn = WETH_ADDR; address tokenOut = WBTC_ADDR; RestrictTransferFrom.TransferType transferType = RestrictTransferFrom.TransferType.None; uint8 partialFillOffset = 12; // filling only half of the quote uint256 amountIn = 1000000000000000000 / 2; bool approvalNeeded = true; uint256 expectedAmountOut = 3617660 / 2; deal(tokenIn, address(bebopExecutor), amountIn); bytes memory params = abi.encodePacked( tokenIn, tokenOut, transferType, partialFillOffset, amountIn * 2, // this is the original amount in approvalNeeded, address(bebopExecutor), bebopCalldata ); uint256 initialTokenOutBalance = IERC20(tokenOut).balanceOf(address(bebopExecutor)); uint256 amountOut = bebopExecutor.swap(amountIn, params); assertEq(amountOut, expectedAmountOut, "Incorrect partial amount out"); assertEq( IERC20(tokenOut).balanceOf(address(bebopExecutor)) - initialTokenOutBalance, expectedAmountOut, "WETH should be at receiver" ); assertEq( IERC20(tokenIn).balanceOf(address(bebopExecutor)), 0, "WBTC left in executor" ); } // Aggregate Order Tests function testAggregateOrder() public { // 20k USDC -> ONDO vm.createSelectFork(vm.rpcUrl("mainnet"), 23126278); bebopExecutor = new BebopExecutorExposed(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); // Quote made manually using the BebopExecutor as the taker and receiver bytes memory bebopCalldata = hex"a2f7489300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000689b715d0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000000000000000000000000000000005a0e0c07568b14a2d2c1b4d196000fc12bc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000ce79b081c0c924cb67848723ed3057234d10fc6b00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000002a65384e777abcfe0000000000000000000000000000000000000000000000002a65384e777abcff0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be300000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000236ddb7a7000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000002713a105900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000001e7dc63f0c1d9d93df4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000021960567af238bcfd0000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041275c4b7c3df4bfa5c33da3443d817cc6ab568ec8b0fddc30445adff2e870cdcd7d8738e23b795c2fb1ee112e12716bcef1cf648bd1ded17ef10ae493d687322e1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004187ef3d632a640b09df5f39b2fb4c5b9afb7ab4f2782fee450b17e2363d27303b45ec55b154a63993106bfc28bb4accc10fb40f7927509fed554fac01a5d88bae1c00000000000000000000000000000000000000000000000000000000000000"; address tokenIn = USDC_ADDR; address tokenOut = ONDO_ADDR; RestrictTransferFrom.TransferType transferType = RestrictTransferFrom.TransferType.None; uint8 partialFillOffset = 2; // filling only half of the quote uint256 amountIn = 20000000000; bool approvalNeeded = true; // maker amounts from quote uint256 expectedAmountOut = (8999445165322964385268 + 9912843438638420000000); deal(tokenIn, address(bebopExecutor), amountIn); bytes memory params = abi.encodePacked( tokenIn, tokenOut, transferType, partialFillOffset, amountIn, approvalNeeded, address(bebopExecutor), bebopCalldata ); uint256 initialTokenOutBalance = IERC20(tokenOut).balanceOf(address(bebopExecutor)); uint256 amountOut = bebopExecutor.swap(amountIn, params); assertEq(amountOut, expectedAmountOut, "Incorrect amount out"); assertEq( IERC20(tokenOut).balanceOf(address(bebopExecutor)) - initialTokenOutBalance, expectedAmountOut, "ONDO should be at receiver" ); assertEq( IERC20(tokenIn).balanceOf(address(bebopExecutor)), 0, "USDC left in executor" ); } function testAggregateOrder_PartialFill() public { // 10k USDC -> ONDO with a quote for 20k USDC vm.createSelectFork(vm.rpcUrl("mainnet"), 23126278); bebopExecutor = new BebopExecutorExposed(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); // Quote made manually using the BebopExecutor as the taker and receiver bytes memory bebopCalldata = hex"a2f7489300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000689b715d0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000000000000000000000000000000005a0e0c07568b14a2d2c1b4d196000fc12bc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000ce79b081c0c924cb67848723ed3057234d10fc6b00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000002a65384e777abcfe0000000000000000000000000000000000000000000000002a65384e777abcff0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be300000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000236ddb7a7000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000002713a105900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000001e7dc63f0c1d9d93df4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000021960567af238bcfd0000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041275c4b7c3df4bfa5c33da3443d817cc6ab568ec8b0fddc30445adff2e870cdcd7d8738e23b795c2fb1ee112e12716bcef1cf648bd1ded17ef10ae493d687322e1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004187ef3d632a640b09df5f39b2fb4c5b9afb7ab4f2782fee450b17e2363d27303b45ec55b154a63993106bfc28bb4accc10fb40f7927509fed554fac01a5d88bae1c00000000000000000000000000000000000000000000000000000000000000"; address tokenIn = USDC_ADDR; address tokenOut = ONDO_ADDR; RestrictTransferFrom.TransferType transferType = RestrictTransferFrom.TransferType.None; uint8 partialFillOffset = 2; // filling only half of the quote uint256 amountIn = 20000000000 / 2; bool approvalNeeded = true; // maker amounts from quote uint256 expectedAmountOut = (8999445165322964385268 + 9912843438638420000000) / 2; deal(tokenIn, address(bebopExecutor), amountIn); bytes memory params = abi.encodePacked( tokenIn, tokenOut, transferType, partialFillOffset, amountIn * 2, // this is the original amount from the quote approvalNeeded, address(bebopExecutor), bebopCalldata ); uint256 initialTokenOutBalance = IERC20(tokenOut).balanceOf(address(bebopExecutor)); uint256 amountOut = bebopExecutor.swap(amountIn, params); assertEq(amountOut, expectedAmountOut, "Incorrect amount out"); assertEq( IERC20(tokenOut).balanceOf(address(bebopExecutor)) - initialTokenOutBalance, expectedAmountOut, "ONDO should be at receiver" ); assertEq( IERC20(tokenIn).balanceOf(address(bebopExecutor)), 1, // because of integer division, there is 1 USDC left in the executor "USDC left in executor" ); } function testInvalidDataLength() public { vm.createSelectFork(vm.rpcUrl("mainnet"), 22667985); bebopExecutor = new BebopExecutorExposed(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); // Create a mock bebop calldata bytes memory bebopCalldata = hex"47fb5891" // swapSingle selector hex"1234567890abcdef"; // some mock data // Create params with correct length first uint256 originalAmountIn = 1e18; bytes memory validParams = abi.encodePacked( WETH_ADDR, USDC_ADDR, uint8(RestrictTransferFrom.TransferType.Transfer), uint8(2), originalAmountIn, true, address(bebopExecutor), bebopCalldata ); // Verify valid params work bebopExecutor.decodeData(validParams); // In the new format, adding extra bytes at the end doesn't fail // because bebopCalldata is variable length at the end // So test with extra bytes should not revert bytes memory paramsWithExtra = abi.encodePacked(validParams, hex"ff"); // This should work as the extra byte becomes part of bebopCalldata bebopExecutor.decodeData(paramsWithExtra); // 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.decodeData(tooShortParams); } } contract TychoRouterForBebopTest is TychoRouterTestSetup { // Override the fork block for Bebop tests function getForkBlock() public pure override returns (uint256) { return 22667986; } function testSingleBebopIntegration() public { // The calldata swaps 200 USDC for ONDO address user = 0xd2068e04Cf586f76EEcE7BA5bEB779D7bB1474A1; deal(USDC_ADDR, user, 200000000); // 200 USDC uint256 expAmountOut = 194477331556159832309; // Expected ONDO amount from quote uint256 ondoBefore = IERC20(ONDO_ADDR).balanceOf(user); vm.startPrank(user); IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max); bytes memory callData = loadCallDataFromFile("test_single_encoding_strategy_bebop"); (bool success,) = tychoRouterAddr.call(callData); assertTrue(success, "Call Failed"); uint256 ondoReceived = IERC20(ONDO_ADDR).balanceOf(user) - ondoBefore; assertEq(ondoReceived, expAmountOut); assertEq( IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 0, "USDC left in router" ); vm.stopPrank(); } function testBebopAggregateIntegration() public { // The calldata swaps 20k USDC for ONDO using multiple market makers address user = 0xd2068e04Cf586f76EEcE7BA5bEB779D7bB1474A1; deal(USDC_ADDR, user, 20000000000); // 20k USDC uint256 expAmountOut = 18699321819466078474202; // Expected ONDO amount from quote uint256 ondoBefore = IERC20(ONDO_ADDR).balanceOf(user); vm.startPrank(user); IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max); bytes memory callData = loadCallDataFromFile( "test_single_encoding_strategy_bebop_aggregate" ); (bool success,) = tychoRouterAddr.call(callData); assertTrue(success, "Call Failed"); uint256 ondoReceived = IERC20(ONDO_ADDR).balanceOf(user) - ondoBefore; assertEq(ondoReceived, expAmountOut); assertEq( IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 0, "USDC left in router" ); vm.stopPrank(); } }