Merge pull request #212 from pedrobergamini/feat/bebop-rfq-encoder-and-executor
feat: Bebop PMM RFQ
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
"ekubo_v2": "0x263DD7AD20983b5E0392bf1F09C4493500EDb333",
|
||||
"vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165",
|
||||
"vm:maverick_v2": "0xF35e3F5F205769B41508A18787b62A21bC80200B",
|
||||
"vm:balancer_v3": "0xec5cE4bF6FbcB7bB0148652c92a4AEC8c1d474Ec"
|
||||
"vm:balancer_v3": "0xec5cE4bF6FbcB7bB0148652c92a4AEC8c1d474Ec",
|
||||
"rfq:bebop": "0xEDCA8A3ACEB5db816d5CF833248d05Ed2784A304"
|
||||
},
|
||||
"base": {
|
||||
"uniswap_v2": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
},
|
||||
"vm:curve": {
|
||||
"native_token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
|
||||
},
|
||||
"rfq:bebop": {
|
||||
"bebop_settlement_address": "0xbbbbbBB520d69a9775E85b458C58c648259FAD5F"
|
||||
}
|
||||
},
|
||||
"base": {},
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ekubo_v2": "0xa0Cb889707d426A7A386870A03bc70d1b0697598",
|
||||
"vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211",
|
||||
"vm:maverick_v2": "0xA4AD4f68d0b91CFD19687c881e50f3A00242828c",
|
||||
"vm:balancer_v3": "0x03A6a84cD762D9707A21605b548aaaB891562aAb"
|
||||
"vm:balancer_v3": "0x03A6a84cD762D9707A21605b548aaaB891562aAb",
|
||||
"rfq:bebop": "0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,11 @@ const executors_to_deploy = {
|
||||
},
|
||||
// Args: Permit2
|
||||
{exchange: "BalancerV3Executor", args: ["0x000000000022D473030F116dDEE9F6B43aC78BA3"]},
|
||||
// Args: Bebop Settlement contract, Permit2
|
||||
{
|
||||
exchange: "BebopExecutor",
|
||||
args: ["0xbbbbbBB520d69a9775E85b458C58c648259FAD5F", "0x000000000022D473030F116dDEE9F6B43aC78BA3"]
|
||||
},
|
||||
],
|
||||
"base": [
|
||||
// Args: Factory, Pool Init Code Hash, Permit2, Fee BPS
|
||||
|
||||
182
foundry/src/executors/BebopExecutor.sol
Normal file
182
foundry/src/executors/BebopExecutor.sol
Normal file
@@ -0,0 +1,182 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "@interfaces/IExecutor.sol";
|
||||
import "../RestrictTransferFrom.sol";
|
||||
import "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
import {
|
||||
IERC20,
|
||||
SafeERC20
|
||||
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
/// @title BebopExecutor
|
||||
/// @notice Executor for Bebop PMM RFQ (Request for Quote) swaps
|
||||
/// @dev Handles Single and Aggregate RFQ swaps through Bebop settlement contract
|
||||
/// @dev Only supports single token in to single token out swaps
|
||||
contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
|
||||
using Math for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
using Address for address;
|
||||
|
||||
/// @notice Function selectors for Bebop settlement methods
|
||||
bytes4 public constant SWAP_AGGREGATE_SELECTOR = 0xa2f74893;
|
||||
|
||||
/// @notice Bebop-specific errors
|
||||
error BebopExecutor__InvalidDataLength();
|
||||
error BebopExecutor__ZeroAddress();
|
||||
|
||||
/// @notice The Bebop settlement contract address
|
||||
address public immutable bebopSettlement;
|
||||
|
||||
constructor(address _bebopSettlement, address _permit2)
|
||||
RestrictTransferFrom(_permit2)
|
||||
{
|
||||
if (_bebopSettlement == address(0)) revert BebopExecutor__ZeroAddress();
|
||||
bebopSettlement = _bebopSettlement;
|
||||
}
|
||||
|
||||
/// @notice Executes a swap through Bebop's PMM RFQ system
|
||||
/// @param givenAmount The amount of input token to swap
|
||||
/// @param data Encoded swap data containing tokens and bebop calldata
|
||||
/// @return calculatedAmount The amount of output token received
|
||||
function swap(uint256 givenAmount, bytes calldata data)
|
||||
external
|
||||
payable
|
||||
virtual
|
||||
override
|
||||
returns (uint256 calculatedAmount)
|
||||
{
|
||||
(
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
TransferType transferType,
|
||||
uint8 partialFillOffset,
|
||||
uint256 originalFilledTakerAmount,
|
||||
bool approvalNeeded,
|
||||
address receiver,
|
||||
bytes memory bebopCalldata
|
||||
) = _decodeData(data);
|
||||
|
||||
_transfer(address(this), transferType, address(tokenIn), givenAmount);
|
||||
|
||||
// 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,
|
||||
partialFillOffset
|
||||
);
|
||||
|
||||
// Approve Bebop settlement to spend tokens if needed
|
||||
if (approvalNeeded) {
|
||||
// slither-disable-next-line unused-return
|
||||
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
|
||||
}
|
||||
|
||||
uint256 balanceBefore = _balanceOf(tokenOut, receiver);
|
||||
uint256 ethValue = tokenIn == address(0) ? givenAmount : 0;
|
||||
|
||||
// Use OpenZeppelin's Address library for safe call with value
|
||||
// This will revert if the call fails
|
||||
// slither-disable-next-line unused-return
|
||||
bebopSettlement.functionCallWithValue(finalCalldata, ethValue);
|
||||
|
||||
uint256 balanceAfter = _balanceOf(tokenOut, receiver);
|
||||
calculatedAmount = balanceAfter - balanceBefore;
|
||||
}
|
||||
|
||||
/// @dev Decodes the packed calldata
|
||||
function _decodeData(bytes calldata data)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
TransferType transferType,
|
||||
uint8 partialFillOffset,
|
||||
uint256 originalFilledTakerAmount,
|
||||
bool approvalNeeded,
|
||||
address receiver,
|
||||
bytes memory bebopCalldata
|
||||
)
|
||||
{
|
||||
// Need at least 95 bytes for the minimum fixed fields
|
||||
// 20 + 20 + 1 + 1 (offset) + 32 (original amount) + 1 (approval) + 20 (receiver) = 95
|
||||
if (data.length < 95) revert BebopExecutor__InvalidDataLength();
|
||||
|
||||
tokenIn = address(bytes20(data[0:20]));
|
||||
tokenOut = address(bytes20(data[20:40]));
|
||||
transferType = TransferType(uint8(data[40]));
|
||||
partialFillOffset = uint8(data[41]);
|
||||
originalFilledTakerAmount = uint256(bytes32(data[42:74]));
|
||||
approvalNeeded = data[74] != 0;
|
||||
receiver = address(bytes20(data[75:95]));
|
||||
bebopCalldata = data[95:];
|
||||
}
|
||||
|
||||
/// @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,
|
||||
uint8 partialFillOffset
|
||||
) internal pure returns (bytes memory) {
|
||||
// Use the offset from Bebop API to locate filledTakerAmount
|
||||
// Position = 4 bytes (selector) + offset * 32 bytes
|
||||
uint256 filledTakerAmountPos = 4 + uint256(partialFillOffset) * 32;
|
||||
|
||||
// Cap the fill amount at what we actually have available
|
||||
uint256 newFilledTakerAmount = originalFilledTakerAmount > givenAmount
|
||||
? givenAmount
|
||||
: originalFilledTakerAmount;
|
||||
|
||||
// If the new filledTakerAmount is the same as the original, return the original calldata
|
||||
if (newFilledTakerAmount == originalFilledTakerAmount) {
|
||||
return bebopCalldata;
|
||||
}
|
||||
|
||||
// Use assembly to modify the filledTakerAmount at the correct position
|
||||
// slither-disable-next-line assembly
|
||||
assembly {
|
||||
// Get pointer to the data portion of the bytes array
|
||||
let dataPtr := add(bebopCalldata, 0x20)
|
||||
|
||||
// Calculate the actual position and store the new value
|
||||
let actualPos := add(dataPtr, filledTakerAmountPos)
|
||||
mstore(actualPos, newFilledTakerAmount)
|
||||
}
|
||||
|
||||
return bebopCalldata;
|
||||
}
|
||||
|
||||
/// @dev Returns the balance of a token or ETH for an account
|
||||
/// @param token The token address, or address(0) for ETH
|
||||
/// @param account The account to get the balance of
|
||||
/// @return balance The balance of the token or ETH for the account
|
||||
function _balanceOf(address token, address account)
|
||||
internal
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return token == address(0)
|
||||
? 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
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
|
||||
import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
|
||||
import {IUnlockCallback} from
|
||||
"@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
|
||||
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
|
||||
import {SafeCast as V4SafeCast} from
|
||||
"@uniswap/v4-core/src/libraries/SafeCast.sol";
|
||||
import {TransientStateLibrary} from
|
||||
"@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
||||
import "../RestrictTransferFrom.sol";
|
||||
@@ -42,7 +43,7 @@ contract UniswapV4Executor is
|
||||
{
|
||||
using SafeERC20 for IERC20;
|
||||
using CurrencyLibrary for Currency;
|
||||
using SafeCast for *;
|
||||
using V4SafeCast for *;
|
||||
using TransientStateLibrary for IPoolManager;
|
||||
|
||||
IPoolManager public immutable poolManager;
|
||||
|
||||
@@ -60,10 +60,9 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
|
||||
ResolvedOrder[] calldata resolvedOrders,
|
||||
bytes calldata callbackData
|
||||
) external onlyRole(REACTOR_ROLE) {
|
||||
require(
|
||||
resolvedOrders.length == 1,
|
||||
UniswapXFiller__BatchExecutionNotSupported()
|
||||
);
|
||||
if (resolvedOrders.length != 1) {
|
||||
revert UniswapXFiller__BatchExecutionNotSupported();
|
||||
}
|
||||
|
||||
ResolvedOrder memory order = resolvedOrders[0];
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ contract Constants is Test, BaseConstants {
|
||||
address WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44);
|
||||
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
|
||||
address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);
|
||||
address ONDO_ADDR = address(0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3);
|
||||
|
||||
// Maverick v2
|
||||
address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e;
|
||||
@@ -124,6 +125,9 @@ contract Constants is Test, BaseConstants {
|
||||
// Permit2
|
||||
address PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
|
||||
|
||||
// Bebop Settlement
|
||||
address BEBOP_SETTLEMENT = 0xbbbbbBB520d69a9775E85b458C58c648259FAD5F;
|
||||
|
||||
// Pool Code Init Hashes
|
||||
bytes32 USV2_POOL_CODE_INIT_HASH =
|
||||
0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;
|
||||
|
||||
@@ -3,6 +3,7 @@ pragma solidity ^0.8.26;
|
||||
|
||||
import "./TychoRouterTestSetup.sol";
|
||||
import "./protocols/UniswapV4Utils.sol";
|
||||
import "@src/executors/BebopExecutor.sol";
|
||||
|
||||
contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
|
||||
function testMultiProtocolIntegration() public {
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma solidity ^0.8.26;
|
||||
// Executors
|
||||
import {BalancerV2Executor} from "../src/executors/BalancerV2Executor.sol";
|
||||
import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol";
|
||||
import {BebopExecutor} from "../src/executors/BebopExecutor.sol";
|
||||
import {CurveExecutor} from "../src/executors/CurveExecutor.sol";
|
||||
import {EkuboExecutor} from "../src/executors/EkuboExecutor.sol";
|
||||
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
|
||||
@@ -73,12 +74,13 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
CurveExecutor public curveExecutor;
|
||||
MaverickV2Executor public maverickv2Executor;
|
||||
BalancerV3Executor public balancerV3Executor;
|
||||
BebopExecutor public bebopExecutor;
|
||||
|
||||
function getForkBlock() public view virtual returns (uint256) {
|
||||
return 22082754;
|
||||
}
|
||||
|
||||
function setUp() public {
|
||||
function setUp() public virtual {
|
||||
uint256 forkBlock = getForkBlock();
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
|
||||
@@ -132,8 +134,9 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
maverickv2Executor =
|
||||
new MaverickV2Executor(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS);
|
||||
balancerV3Executor = new BalancerV3Executor(PERMIT2_ADDRESS);
|
||||
bebopExecutor = new BebopExecutor(BEBOP_SETTLEMENT, PERMIT2_ADDRESS);
|
||||
|
||||
address[] memory executors = new address[](9);
|
||||
address[] memory executors = new address[](10);
|
||||
executors[0] = address(usv2Executor);
|
||||
executors[1] = address(usv3Executor);
|
||||
executors[2] = address(pancakev3Executor);
|
||||
@@ -143,6 +146,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
executors[6] = address(curveExecutor);
|
||||
executors[7] = address(maverickv2Executor);
|
||||
executors[8] = address(balancerV3Executor);
|
||||
executors[9] = address(bebopExecutor);
|
||||
|
||||
return executors;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
470
foundry/test/protocols/Bebop.t.sol
Normal file
470
foundry/test/protocols/Bebop.t.sol
Normal file
@@ -0,0 +1,470 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ use tycho_common::models::Chain;
|
||||
use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::swap_encoder::swap_encoders::{
|
||||
BalancerV2SwapEncoder, BalancerV3SwapEncoder, CurveSwapEncoder, EkuboSwapEncoder,
|
||||
MaverickV2SwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder, UniswapV4SwapEncoder,
|
||||
BalancerV2SwapEncoder, BalancerV3SwapEncoder, BebopSwapEncoder, CurveSwapEncoder,
|
||||
EkuboSwapEncoder, MaverickV2SwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder,
|
||||
UniswapV4SwapEncoder,
|
||||
},
|
||||
swap_encoder::SwapEncoder,
|
||||
};
|
||||
@@ -87,6 +88,9 @@ impl SwapEncoderBuilder {
|
||||
self.chain,
|
||||
self.config,
|
||||
)?)),
|
||||
"rfq:bebop" => {
|
||||
Ok(Box::new(BebopSwapEncoder::new(self.executor_address, self.chain, self.config)?))
|
||||
}
|
||||
_ => Err(EncodingError::FatalError(format!(
|
||||
"Unknown protocol system: {}",
|
||||
self.protocol_system
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use alloy::{
|
||||
primitives::{Address, Bytes as AlloyBytes, U8},
|
||||
primitives::{Address, Bytes as AlloyBytes, U256, U8},
|
||||
sol_types::SolValue,
|
||||
};
|
||||
use serde_json::from_str;
|
||||
@@ -634,6 +634,117 @@ impl SwapEncoder for BalancerV3SwapEncoder {
|
||||
fn executor_address(&self) -> &str {
|
||||
&self.executor_address
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn SwapEncoder> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a swap on Bebop (PMM RFQ) through the given executor address.
|
||||
///
|
||||
/// Bebop uses a Request-for-Quote model where quotes are obtained off-chain
|
||||
/// and settled on-chain. This encoder supports PMM RFQ execution.
|
||||
///
|
||||
/// # Fields
|
||||
/// * `executor_address` - The address of the executor contract that will perform the swap.
|
||||
/// * `settlement_address` - The address of the Bebop settlement contract.
|
||||
#[derive(Clone)]
|
||||
pub struct BebopSwapEncoder {
|
||||
executor_address: String,
|
||||
settlement_address: String,
|
||||
}
|
||||
|
||||
impl SwapEncoder for BebopSwapEncoder {
|
||||
fn new(
|
||||
executor_address: String,
|
||||
_chain: Chain,
|
||||
config: Option<HashMap<String, String>>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
let config = config.ok_or(EncodingError::FatalError(
|
||||
"Missing bebop specific addresses in config".to_string(),
|
||||
))?;
|
||||
let settlement_address = config
|
||||
.get("bebop_settlement_address")
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"Missing bebop settlement address in config".to_string(),
|
||||
))?
|
||||
.to_string();
|
||||
Ok(Self { executor_address, settlement_address })
|
||||
}
|
||||
|
||||
fn encode_swap(
|
||||
&self,
|
||||
swap: &Swap,
|
||||
encoding_context: &EncodingContext,
|
||||
) -> Result<Vec<u8>, EncodingError> {
|
||||
let token_in = bytes_to_address(&swap.token_in)?;
|
||||
let token_out = bytes_to_address(&swap.token_out)?;
|
||||
|
||||
let token_approvals_manager = ProtocolApprovalsManager::new()?;
|
||||
let approval_needed: bool;
|
||||
|
||||
if let Some(router_address) = &encoding_context.router_address {
|
||||
let tycho_router_address = bytes_to_address(router_address)?;
|
||||
let token_to_approve = token_in;
|
||||
let settlement_address = Address::from_str(&self.settlement_address)
|
||||
.map_err(|_| EncodingError::FatalError("Invalid settlement address".to_string()))?;
|
||||
|
||||
// Native ETH doesn't need approval, only ERC20 tokens do
|
||||
if token_to_approve == Address::ZERO {
|
||||
approval_needed = false;
|
||||
} else {
|
||||
approval_needed = token_approvals_manager.approval_needed(
|
||||
token_to_approve,
|
||||
tycho_router_address,
|
||||
settlement_address,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
approval_needed = true;
|
||||
}
|
||||
|
||||
// The user data required for Bebop is
|
||||
// partial_fill_offset (u8) | original_taker_amount (U256) | calldata (bytes (selector ABI
|
||||
// encoded params))
|
||||
let user_data = swap.user_data.clone().ok_or_else(|| {
|
||||
EncodingError::InvalidInput("Bebop swaps require user_data with calldata".to_string())
|
||||
})?;
|
||||
|
||||
if user_data.len() < 37 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain offset and Bebop calldata".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let partial_fill_offset = user_data[0];
|
||||
let original_filled_taker_amount = U256::from_be_slice(&user_data[1..33]);
|
||||
|
||||
// The calldata should be for either swapSingle or swapAggregate
|
||||
let bebop_calldata = user_data[33..].to_vec();
|
||||
|
||||
let receiver = bytes_to_address(&encoding_context.receiver)?;
|
||||
|
||||
// Encode packed data for the executor
|
||||
// Format: token_in | token_out | transfer_type | partial_fill_offset |
|
||||
// original_filled_taker_amount | approval_needed | receiver | bebop_calldata
|
||||
let args = (
|
||||
token_in,
|
||||
token_out,
|
||||
(encoding_context.transfer_type as u8).to_be_bytes(),
|
||||
partial_fill_offset.to_be_bytes(),
|
||||
original_filled_taker_amount.to_be_bytes::<32>(),
|
||||
(approval_needed as u8).to_be_bytes(),
|
||||
receiver,
|
||||
&bebop_calldata[..],
|
||||
);
|
||||
|
||||
Ok(args.abi_encode_packed())
|
||||
}
|
||||
|
||||
fn executor_address(&self) -> &str {
|
||||
&self.executor_address
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn SwapEncoder> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
@@ -1610,4 +1721,144 @@ mod tests {
|
||||
write_calldata_to_file("test_encode_maverick_v2", hex_swap.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
mod bebop {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_single() {
|
||||
// 200 USDC -> ONDO
|
||||
let bebop_calldata= Bytes::from_str("0x4dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000").unwrap();
|
||||
let original_taker_amount = U256::from_str("200000000").unwrap();
|
||||
// partialFillOffset 12 for swapSingle
|
||||
let mut user_data = vec![12u8];
|
||||
user_data.extend_from_slice(&original_taker_amount.to_be_bytes::<32>());
|
||||
user_data.extend_from_slice(&bebop_calldata);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
protocol_system: String::from("rfq:bebop"),
|
||||
static_attributes: HashMap::new(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO
|
||||
|
||||
let swap = SwapBuilder::new(bebop_component, token_in.clone(), token_out.clone())
|
||||
.user_data(Bytes::from(user_data))
|
||||
.build();
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"),
|
||||
exact_out: false,
|
||||
router_address: Some(Bytes::zero(20)),
|
||||
group_token_in: token_in.clone(),
|
||||
group_token_out: token_out.clone(),
|
||||
transfer_type: TransferType::Transfer,
|
||||
};
|
||||
|
||||
let encoder = BebopSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
Chain::Ethereum,
|
||||
Some(HashMap::from([(
|
||||
"bebop_settlement_address".to_string(),
|
||||
"0xbbbbbBB520d69a9775E85b458C58c648259FAD5F".to_string(),
|
||||
)])),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoded_swap = encoder
|
||||
.encode_swap(&swap, &encoding_context)
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
let expected_swap = String::from(concat!(
|
||||
// token in
|
||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
// token out
|
||||
"faba6f8e4a5e8ab82f62fe7c39859fa577269be3",
|
||||
// transfer type
|
||||
"01",
|
||||
// partiall filled offset
|
||||
"0c",
|
||||
// original taker amount
|
||||
"000000000000000000000000000000000000000000000000000000000bebc200",
|
||||
// approval needed
|
||||
"01",
|
||||
//receiver,
|
||||
"c5564c13a157e6240659fb81882a28091add8670",
|
||||
));
|
||||
assert_eq!(hex_swap, expected_swap + &bebop_calldata.to_string()[2..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_aggregate() {
|
||||
// 20k USDC -> ONDO
|
||||
let bebop_calldata= Bytes::from_str("0xa2f7489300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000689b78880000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000005a060a5c2aaaaa2fe2cda34423cac76a84c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000ce79b081c0c924cb67848723ed3057234d10fc6b00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000002901f2d62bb356ca0000000000000000000000000000000000000000000000002901f2d62bb356cb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000044f83c726000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000589400da00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000003aa5f96046644f6e37a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000004b51a26526ddbeec60000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000417ab4332f2b091d87d56d04eee35dd49452782c782de71608c0425c5ae41f1d7e147173851c870d76720ce07d45cd8622352716b1c7965819ee2bf8c573c499ae1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410c8da2637aa929e11caff9afdfc4c489320c6dba77cc934d88ba8956e365fd1d48983087c6e474bbb828181cdfdd17317c4c9c3ee4bc98e3769d0c05cc7a285e1c00000000000000000000000000000000000000000000000000000000000000").unwrap();
|
||||
let original_taker_amount = U256::from_str("20000000000").unwrap();
|
||||
|
||||
// partialFillOffset is 2 for swapAggregate
|
||||
let mut user_data = vec![2u8];
|
||||
user_data.extend_from_slice(&original_taker_amount.to_be_bytes::<32>());
|
||||
user_data.extend_from_slice(&bebop_calldata);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
protocol_system: String::from("rfq:bebop"),
|
||||
static_attributes: HashMap::new(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO
|
||||
|
||||
let swap = SwapBuilder::new(bebop_component, token_in.clone(), token_out.clone())
|
||||
.user_data(Bytes::from(user_data))
|
||||
.build();
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"),
|
||||
exact_out: false,
|
||||
router_address: Some(Bytes::zero(20)),
|
||||
group_token_in: token_in.clone(),
|
||||
group_token_out: token_out.clone(),
|
||||
transfer_type: TransferType::Transfer,
|
||||
};
|
||||
|
||||
let encoder = BebopSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
Chain::Ethereum,
|
||||
Some(HashMap::from([(
|
||||
"bebop_settlement_address".to_string(),
|
||||
"0xbbbbbBB520d69a9775E85b458C58c648259FAD5F".to_string(),
|
||||
)])),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoded_swap = encoder
|
||||
.encode_swap(&swap, &encoding_context)
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
let expected_swap = String::from(concat!(
|
||||
// token in
|
||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
// token out
|
||||
"faba6f8e4a5e8ab82f62fe7c39859fa577269be3",
|
||||
// transfer type
|
||||
"01",
|
||||
// partiall filled offset
|
||||
"02",
|
||||
// original taker amount
|
||||
"00000000000000000000000000000000000000000000000000000004a817c800",
|
||||
// approval needed
|
||||
"01",
|
||||
//receiver,
|
||||
"c5564c13a157e6240659fb81882a28091add8670",
|
||||
));
|
||||
|
||||
assert_eq!(hex_swap, expected_swap + &bebop_calldata.to_string()[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ pub mod encoding;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use alloy::{primitives::B256, signers::local::PrivateKeySigner};
|
||||
use alloy::{
|
||||
primitives::{B256, U256},
|
||||
signers::local::PrivateKeySigner,
|
||||
};
|
||||
use tycho_common::{models::Chain, Bytes};
|
||||
use tycho_execution::encoding::{
|
||||
evm::encoder_builders::TychoRouterEncoderBuilder, models::UserTransferType,
|
||||
@@ -46,6 +49,10 @@ pub fn usdt() -> Bytes {
|
||||
Bytes::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap()
|
||||
}
|
||||
|
||||
pub fn ondo() -> Bytes {
|
||||
Bytes::from_str("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3").unwrap()
|
||||
}
|
||||
|
||||
pub fn get_signer() -> PrivateKeySigner {
|
||||
// Set up a mock private key for signing (Alice's pk in our contract tests)
|
||||
let private_key =
|
||||
@@ -64,3 +71,19 @@ pub fn get_tycho_router_encoder(user_transfer_type: UserTransferType) -> Box<dyn
|
||||
.build()
|
||||
.expect("Failed to build encoder")
|
||||
}
|
||||
|
||||
/// Builds the complete Bebop calldata in the format expected by the encoder
|
||||
/// Returns: [ partial_fill_offset (u8) | original_taker_amount (U256) | calldata (bytes (selector +
|
||||
/// ABI encoded params)) ]
|
||||
pub fn build_bebop_calldata(
|
||||
calldata: &[u8],
|
||||
partial_fill_offset: u8,
|
||||
original_taker_amount: U256,
|
||||
) -> Bytes {
|
||||
let mut user_data = Vec::with_capacity(1 + 32 + calldata.len());
|
||||
user_data.push(partial_fill_offset);
|
||||
user_data.extend_from_slice(&original_taker_amount.to_be_bytes::<32>());
|
||||
user_data.extend_from_slice(calldata);
|
||||
|
||||
Bytes::from(user_data)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
mod common;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use alloy::hex::encode;
|
||||
use alloy::{hex, hex::encode};
|
||||
use num_bigint::{BigInt, BigUint};
|
||||
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||
use tycho_execution::encoding::{
|
||||
evm::utils::write_calldata_to_file,
|
||||
models::{Solution, Swap, UserTransferType},
|
||||
evm::utils::{biguint_to_u256, write_calldata_to_file},
|
||||
models::{Solution, Swap, SwapBuilder, UserTransferType},
|
||||
};
|
||||
|
||||
use crate::common::{
|
||||
encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder, pepe,
|
||||
usdc, weth,
|
||||
build_bebop_calldata, encoding::encode_tycho_router_call, eth, eth_chain, get_signer,
|
||||
get_tycho_router_encoder, ondo, pepe, usdc, weth,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_ekubo() {
|
||||
// ETH ──(EKUBO)──> USDC
|
||||
@@ -590,3 +591,126 @@ fn test_single_encoding_strategy_balancer_v3() {
|
||||
let hex_calldata = encode(&calldata);
|
||||
write_calldata_to_file("test_single_encoding_strategy_balancer_v3", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_bebop() {
|
||||
// The quote was done separately where the sender is the router and the receiver is a random
|
||||
// user
|
||||
let _router = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap();
|
||||
let user = Bytes::from_str("0xd2068e04cf586f76eece7ba5beb779d7bb1474a1").unwrap();
|
||||
|
||||
let token_in = usdc();
|
||||
let token_out = ondo();
|
||||
let amount_in = BigUint::from_str("200000000").unwrap(); // 200 USDC
|
||||
let amount_out = BigUint::from_str("194477331556159832309").unwrap(); // 203.8 ONDO
|
||||
let partial_fill_offset = 12;
|
||||
|
||||
let calldata = Bytes::from_str("0x4dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000").unwrap();
|
||||
let user_data =
|
||||
build_bebop_calldata(&calldata, partial_fill_offset, biguint_to_u256(&amount_in));
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
protocol_system: String::from("rfq:bebop"),
|
||||
static_attributes: HashMap::new(), // No static attributes needed
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let swap = SwapBuilder::new(bebop_component, token_in.clone(), token_out.clone())
|
||||
.user_data(user_data)
|
||||
.build();
|
||||
|
||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: token_in,
|
||||
given_amount: amount_in,
|
||||
checked_token: token_out,
|
||||
checked_amount: amount_out, // Expected output amount
|
||||
sender: user.clone(),
|
||||
receiver: user,
|
||||
swaps: vec![swap],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let encoded_solution = encoder
|
||||
.encode_solutions(vec![solution.clone()])
|
||||
.unwrap()[0]
|
||||
.clone();
|
||||
|
||||
let calldata = encode_tycho_router_call(
|
||||
eth_chain().id(),
|
||||
encoded_solution,
|
||||
&solution,
|
||||
&UserTransferType::TransferFrom,
|
||||
ð(),
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.data;
|
||||
let hex_calldata = hex::encode(&calldata);
|
||||
write_calldata_to_file("test_single_encoding_strategy_bebop", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_bebop_aggregate() {
|
||||
// The quote was done separately where the sender is the router and the receiver is a random
|
||||
// user
|
||||
let _router = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap();
|
||||
let user = Bytes::from_str("0xd2068e04cf586f76eece7ba5beb779d7bb1474a1").unwrap();
|
||||
|
||||
let token_in = usdc();
|
||||
let token_out = ondo();
|
||||
let amount_in = BigUint::from_str("20000000000").unwrap(); // 20k USDC
|
||||
let amount_out = BigUint::from_str("18699321819466078474202").unwrap(); // 203.8 ONDO
|
||||
let partial_fill_offset = 2;
|
||||
|
||||
let calldata = Bytes::from_str("0xa2f7489300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000689b78880000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000005a060a5c2aaaaa2fe2cda34423cac76a84c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000ce79b081c0c924cb67848723ed3057234d10fc6b00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000002901f2d62bb356ca0000000000000000000000000000000000000000000000002901f2d62bb356cb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000044f83c726000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000589400da00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000003aa5f96046644f6e37a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000004b51a26526ddbeec60000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000417ab4332f2b091d87d56d04eee35dd49452782c782de71608c0425c5ae41f1d7e147173851c870d76720ce07d45cd8622352716b1c7965819ee2bf8c573c499ae1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410c8da2637aa929e11caff9afdfc4c489320c6dba77cc934d88ba8956e365fd1d48983087c6e474bbb828181cdfdd17317c4c9c3ee4bc98e3769d0c05cc7a285e1c00000000000000000000000000000000000000000000000000000000000000").unwrap();
|
||||
let user_data =
|
||||
build_bebop_calldata(&calldata, partial_fill_offset, biguint_to_u256(&amount_in));
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
protocol_system: String::from("rfq:bebop"),
|
||||
static_attributes: HashMap::new(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let swap = SwapBuilder::new(bebop_component, token_in.clone(), token_out.clone())
|
||||
.user_data(user_data)
|
||||
.build();
|
||||
|
||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: token_in.clone(),
|
||||
given_amount: amount_in,
|
||||
checked_token: token_out,
|
||||
checked_amount: amount_out,
|
||||
sender: user.clone(),
|
||||
receiver: user,
|
||||
swaps: vec![swap],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let encoded_solution = encoder
|
||||
.encode_solutions(vec![solution.clone()])
|
||||
.unwrap()[0]
|
||||
.clone();
|
||||
|
||||
let calldata = encode_tycho_router_call(
|
||||
eth_chain().id(),
|
||||
encoded_solution,
|
||||
&solution,
|
||||
&UserTransferType::TransferFrom,
|
||||
ð(),
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.data;
|
||||
let hex_calldata = hex::encode(&calldata);
|
||||
|
||||
write_calldata_to_file("test_single_encoding_strategy_bebop_aggregate", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user