Merge branch 'main' into rfqs/tnl/ENG-4798-arc-protocol-state
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
||||
## [0.117.0](https://github.com/propeller-heads/tycho-execution/compare/0.116.0...0.117.0) (2025-08-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* implement `SwapEncoder` for `Hashflow` ([93db953](https://github.com/propeller-heads/tycho-execution/commit/93db953c620f4d52e8852ff8148f2dfdbc580029))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* in bebop's encode_swap, fail early if router address is not present ([c013bf7](https://github.com/propeller-heads/tycho-execution/commit/c013bf707225b171e76f3f3dac88624b6a3458dc))
|
||||
* in hashflow's encode_swap, fail early if router address is not present ([c506f2c](https://github.com/propeller-heads/tycho-execution/commit/c506f2c048a20d1ffd066d2903d2cc469fd167ed))
|
||||
* in HashflowExecutor, _balanceOf must use `trader` address instead of the executor's to get the balance ([a09d648](https://github.com/propeller-heads/tycho-execution/commit/a09d648f3c50f87b392c7c5eb14af07307c5ccea))
|
||||
|
||||
## [0.116.0](https://github.com/propeller-heads/tycho-execution/compare/0.115.0...0.116.0) (2025-08-19)
|
||||
|
||||
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4659,7 +4659,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tycho-execution"
|
||||
version = "0.116.0"
|
||||
version = "0.117.0"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"async-trait",
|
||||
|
||||
18
Cargo.toml
18
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tycho-execution"
|
||||
version = "0.116.0"
|
||||
version = "0.117.0"
|
||||
edition = "2021"
|
||||
description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors."
|
||||
repository = "https://github.com/propeller-heads/tycho-execution"
|
||||
@@ -11,12 +11,12 @@ license = "MIT"
|
||||
categories = ["finance", "cryptography::cryptocurrencies"]
|
||||
readme = "README.md"
|
||||
exclude = [
|
||||
"foundry/*",
|
||||
"foundry",
|
||||
"tests/*",
|
||||
"tests/common",
|
||||
".github/*",
|
||||
".gitmodules",
|
||||
"foundry/*",
|
||||
"foundry",
|
||||
"tests/*",
|
||||
"tests/common",
|
||||
".github/*",
|
||||
".gitmodules",
|
||||
]
|
||||
|
||||
[[bin]]
|
||||
@@ -37,7 +37,7 @@ tokio = { version = "1.38.0", features = ["full"] }
|
||||
chrono = "0.4.39"
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
once_cell = "1.20.2"
|
||||
tycho-common = ">0.81.5"
|
||||
tycho-common = ">=0.81.6"
|
||||
alloy = { version = "1.0.6", features = ["providers", "rpc-types-eth", "eip712", "signer-local", "node-bindings"], optional = true }
|
||||
async-trait = { version = "0.1.88", optional = true }
|
||||
|
||||
@@ -52,4 +52,4 @@ fork-tests = []
|
||||
test-utils = ["async-trait"]
|
||||
|
||||
[profile.bench]
|
||||
debug = true
|
||||
debug = true
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165",
|
||||
"vm:maverick_v2": "0xF35e3F5F205769B41508A18787b62A21bC80200B",
|
||||
"vm:balancer_v3": "0xec5cE4bF6FbcB7bB0148652c92a4AEC8c1d474Ec",
|
||||
"rfq:bebop": "0xFE42BFb115eD9671011cA52BDD23A52A2e077a7c"
|
||||
"rfq:bebop": "0xFE42BFb115eD9671011cA52BDD23A52A2e077a7c",
|
||||
"rfq:hashflow": "0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"
|
||||
},
|
||||
"base": {
|
||||
"uniswap_v2": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700",
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"rfq:bebop": {
|
||||
"bebop_settlement_address": "0xbbbbbBB520d69a9775E85b458C58c648259FAD5F",
|
||||
"native_token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
|
||||
},
|
||||
"rfq:hashflow": {
|
||||
"hashflow_router_address": "0x55084eE0fEf03f14a305cd24286359A35D735151"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
@@ -18,4 +21,4 @@
|
||||
}
|
||||
},
|
||||
"unichain": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211",
|
||||
"vm:maverick_v2": "0xA4AD4f68d0b91CFD19687c881e50f3A00242828c",
|
||||
"vm:balancer_v3": "0x03A6a84cD762D9707A21605b548aaaB891562aAb",
|
||||
"rfq:bebop": "0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF"
|
||||
"rfq:bebop": "0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF",
|
||||
"rfq:hashflow": "0x15cF58144EF33af1e14b5208015d11F9143E27b9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,20 @@ interface IHashflowRouter {
|
||||
contract HashflowExecutor is IExecutor, RestrictTransferFrom {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address public constant HASHFLOW_ROUTER =
|
||||
0x55084eE0fEf03f14a305cd24286359A35D735151;
|
||||
address public constant NATIVE_TOKEN =
|
||||
0x0000000000000000000000000000000000000000;
|
||||
|
||||
constructor(address _permit2) RestrictTransferFrom(_permit2) {}
|
||||
/// @notice The Hashflow router address
|
||||
address public immutable hashflowRouter;
|
||||
|
||||
constructor(address _hashflowRouter, address _permit2)
|
||||
RestrictTransferFrom(_permit2)
|
||||
{
|
||||
if (_hashflowRouter == address(0)) {
|
||||
revert HashflowExecutor__InvalidHashflowRouter();
|
||||
}
|
||||
hashflowRouter = _hashflowRouter;
|
||||
}
|
||||
|
||||
function swap(uint256 givenAmount, bytes calldata data)
|
||||
external
|
||||
@@ -60,7 +68,7 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom {
|
||||
if (approvalNeeded && quote.baseToken != NATIVE_TOKEN) {
|
||||
// slither-disable-next-line unused-return
|
||||
IERC20(quote.baseToken).forceApprove(
|
||||
HASHFLOW_ROUTER, type(uint256).max
|
||||
hashflowRouter, type(uint256).max
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,9 +79,9 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom {
|
||||
_transfer(
|
||||
address(this), transferType, address(quote.baseToken), givenAmount
|
||||
);
|
||||
uint256 balanceBefore = _balanceOf(quote.quoteToken);
|
||||
IHashflowRouter(HASHFLOW_ROUTER).tradeRFQT{value: ethValue}(quote);
|
||||
uint256 balanceAfter = _balanceOf(quote.quoteToken);
|
||||
uint256 balanceBefore = _balanceOf(quote.trader, quote.quoteToken);
|
||||
IHashflowRouter(hashflowRouter).tradeRFQT{value: ethValue}(quote);
|
||||
uint256 balanceAfter = _balanceOf(quote.trader, quote.quoteToken);
|
||||
calculatedAmount = balanceAfter - balanceBefore;
|
||||
}
|
||||
|
||||
@@ -86,35 +94,37 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom {
|
||||
TransferType transferType
|
||||
)
|
||||
{
|
||||
if (data.length != 347) {
|
||||
if (data.length != 327) {
|
||||
revert HashflowExecutor__InvalidDataLength();
|
||||
}
|
||||
|
||||
approvalNeeded = data[0] != 0;
|
||||
transferType = TransferType(uint8(data[1]));
|
||||
transferType = TransferType(uint8(data[0]));
|
||||
approvalNeeded = data[1] != 0;
|
||||
|
||||
quote.pool = address(bytes20(data[2:22]));
|
||||
quote.externalAccount = address(bytes20(data[22:42]));
|
||||
quote.trader = address(bytes20(data[42:62]));
|
||||
quote.effectiveTrader = address(bytes20(data[62:82]));
|
||||
quote.baseToken = address(bytes20(data[82:102]));
|
||||
quote.quoteToken = address(bytes20(data[102:122]));
|
||||
quote.effectiveBaseTokenAmount = 0; // Not included in the calldata, set in the swap function
|
||||
quote.baseTokenAmount = uint256(bytes32(data[122:154]));
|
||||
quote.quoteTokenAmount = uint256(bytes32(data[154:186]));
|
||||
quote.quoteExpiry = uint256(bytes32(data[186:218]));
|
||||
quote.nonce = uint256(bytes32(data[218:250]));
|
||||
quote.txid = bytes32(data[250:282]);
|
||||
quote.signature = data[282:347];
|
||||
// Assumes we never set the effectiveTrader when requesting a quote.
|
||||
quote.effectiveTrader = quote.trader;
|
||||
quote.baseToken = address(bytes20(data[62:82]));
|
||||
quote.quoteToken = address(bytes20(data[82:102]));
|
||||
// Not included in the calldata. Will be set in the swap function.
|
||||
quote.effectiveBaseTokenAmount = 0;
|
||||
quote.baseTokenAmount = uint256(bytes32(data[102:134]));
|
||||
quote.quoteTokenAmount = uint256(bytes32(data[134:166]));
|
||||
quote.quoteExpiry = uint256(bytes32(data[166:198]));
|
||||
quote.nonce = uint256(bytes32(data[198:230]));
|
||||
quote.txid = bytes32(data[230:262]);
|
||||
quote.signature = data[262:327];
|
||||
}
|
||||
|
||||
function _balanceOf(address token)
|
||||
function _balanceOf(address trader, address token)
|
||||
internal
|
||||
view
|
||||
returns (uint256 balance)
|
||||
{
|
||||
balance = token == NATIVE_TOKEN
|
||||
? address(this).balance
|
||||
: IERC20(token).balanceOf(address(this));
|
||||
? trader.balance
|
||||
: IERC20(token).balanceOf(trader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,9 @@ contract Constants is Test, BaseConstants {
|
||||
// Bebop Settlement
|
||||
address BEBOP_SETTLEMENT = 0xbbbbbBB520d69a9775E85b458C58c648259FAD5F;
|
||||
|
||||
// Hashflow Router
|
||||
address HASHFLOW_ROUTER = 0x55084eE0fEf03f14a305cd24286359A35D735151;
|
||||
|
||||
// Pool Code Init Hashes
|
||||
bytes32 USV2_POOL_CODE_INIT_HASH =
|
||||
0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;
|
||||
|
||||
@@ -547,3 +547,40 @@ contract TychoRouterSequentialSwapTestForBebop is TychoRouterTestSetup {
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
}
|
||||
|
||||
contract TychoRouterSequentialSwapTestForHashflow is TychoRouterTestSetup {
|
||||
function getForkBlock() public pure override returns (uint256) {
|
||||
return 23175437;
|
||||
}
|
||||
|
||||
function testUSV3HashflowIntegration() public {
|
||||
// Performs a sequential swap from WETH to WBTC through USDC using USV3 and Hashflow RFQ
|
||||
//
|
||||
// WETH ──(USV3)──> USDC ───(Hashflow RFQ)──> WBTC
|
||||
|
||||
// The Uniswap pool outputs:
|
||||
// - 1 weth -> 4322430557 USDC
|
||||
// The Hashflow tradeRFQT call expects:
|
||||
// - 4308094737 USDC input -> 3714751 WBTC output
|
||||
// The difference in USDC (14335820) will stay in the TychoRouter contract
|
||||
|
||||
uint256 amountIn = 1 ether;
|
||||
uint256 expectedAmountOut = 3714751;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData = loadCallDataFromFile("test_uniswap_v3_hashflow");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, expectedAmountOut);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 14335820);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 {HashflowExecutor} from "../src/executors/HashflowExecutor.sol";
|
||||
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
|
||||
import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol";
|
||||
import {
|
||||
@@ -75,6 +76,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
MaverickV2Executor public maverickv2Executor;
|
||||
BalancerV3Executor public balancerV3Executor;
|
||||
BebopExecutor public bebopExecutor;
|
||||
HashflowExecutor public hashflowExecutor;
|
||||
|
||||
function getForkBlock() public view virtual returns (uint256) {
|
||||
return 22082754;
|
||||
@@ -135,8 +137,10 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
new MaverickV2Executor(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS);
|
||||
balancerV3Executor = new BalancerV3Executor(PERMIT2_ADDRESS);
|
||||
bebopExecutor = new BebopExecutor(BEBOP_SETTLEMENT, PERMIT2_ADDRESS);
|
||||
hashflowExecutor =
|
||||
new HashflowExecutor(HASHFLOW_ROUTER, PERMIT2_ADDRESS);
|
||||
|
||||
address[] memory executors = new address[](10);
|
||||
address[] memory executors = new address[](11);
|
||||
executors[0] = address(usv2Executor);
|
||||
executors[1] = address(usv3Executor);
|
||||
executors[2] = address(pancakev3Executor);
|
||||
@@ -147,6 +151,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
executors[7] = address(maverickv2Executor);
|
||||
executors[8] = address(balancerV3Executor);
|
||||
executors[9] = address(bebopExecutor);
|
||||
executors[10] = address(hashflowExecutor);
|
||||
|
||||
return executors;
|
||||
}
|
||||
|
||||
@@ -38,3 +38,5 @@ test_single_encoding_strategy_bebop_aggregate:5c4b639c00000000000000000000000000
|
||||
test_single_encoding_strategy_bebop:5c4b639c000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be300000000000000000000000000000000000000000000000a8aea46aa4ec5c0f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002d7d6bbde9174b1cdaa358d2cf4d57d1a9f7178fbffa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48faba6f8e4a5e8ab82f62fe7c39859fa577269be3000c000000000000000000000000000000000000000000000000000000000bebc20001d2068e04cf586f76eece7ba5beb779d7bb1474a14dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
test_sequential_swap_strategy_encoder_unwrap:51bcc7b600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000068c5498700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000689dc38f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041192fc75d79a3d76bcf3c3d193cf769446abc98ff76ce2a1de183e7e46d80073836cd6ceedc30f98085188eab1098ca2f0ef03c25ebaa69cd2758988263e563c41b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48004375dff511095cc5a197a54140a24efef3a416bb2b8038a1640196fbe3e38816f3e67cba72d940000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950102000000000000000000000000000000000000000000000000
|
||||
test_sequential_swap_usx:0101e21dd0d300000000000000000000000000000000000000000000006c6b935b8bbd4000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000769cfd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d9da78b6a5bedca287aa5d49613ba36b90c15c40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470b6b175474e89094c44da98b954eedeac495271d0fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000643ede3eca2a72b3aecc820e955b36f38437d013955777d92f208679db4b9778590fa3cab3ac9e2168010000692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48dac17f958d2ee523a2206206994597c13d831ec70000646d9da78b6a5bedca287aa5d49613ba36b90c15c43416cf6c708da44db2624d63ea0aaef7113527c6010100000000000000000000
|
||||
test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001c800692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000015b15cf58144ef33af1e14b5208015d11f9143e27b90201478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c000000000000000000000000000000000000000000000000
|
||||
test_single_encoding_strategy_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000015b15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "../TychoRouterTestSetup.sol";
|
||||
import "@src/executors/HashflowExecutor.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
|
||||
contract HashflowUtils is Test {
|
||||
constructor() {}
|
||||
@@ -14,12 +15,11 @@ contract HashflowUtils is Test {
|
||||
RestrictTransferFrom.TransferType transferType
|
||||
) internal pure returns (bytes memory) {
|
||||
return abi.encodePacked(
|
||||
approvalNeeded, // needsApproval (1 byte)
|
||||
uint8(transferType), // transferType (1 byte)
|
||||
approvalNeeded, // needsApproval (1 byte)
|
||||
quote.pool, // pool (20 bytes)
|
||||
quote.externalAccount, // externalAccount (20 bytes)
|
||||
quote.trader, // trader (20 bytes)
|
||||
quote.effectiveTrader, // effectiveTrader (20 bytes)
|
||||
quote.baseToken, // baseToken (20 bytes)
|
||||
quote.quoteToken, // quoteToken (20 bytes)
|
||||
quote.baseTokenAmount, // baseTokenAmount (32 bytes)
|
||||
@@ -51,9 +51,9 @@ contract HashflowExecutorECR20Test is Constants, HashflowUtils {
|
||||
IERC20 USDC = IERC20(USDC_ADDR);
|
||||
|
||||
function setUp() public {
|
||||
forkBlock = 23124977; // Using expiry date: 1755001853, ECR20
|
||||
forkBlock = 23188416; // Using expiry date: 1755766775, ECR20
|
||||
vm.createSelectFork("mainnet", forkBlock);
|
||||
executor = new HashflowExecutorExposed(PERMIT2_ADDRESS);
|
||||
executor = new HashflowExecutorExposed(HASHFLOW_ROUTER, PERMIT2_ADDRESS);
|
||||
}
|
||||
|
||||
function testDecodeParams() public view {
|
||||
@@ -123,54 +123,54 @@ contract HashflowExecutorECR20Test is Constants, HashflowUtils {
|
||||
}
|
||||
|
||||
function testSwapNoSlippage() public {
|
||||
address trader = address(executor);
|
||||
address trader = address(ALICE);
|
||||
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
|
||||
uint256 amountIn = quote.baseTokenAmount;
|
||||
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
|
||||
|
||||
deal(USDC_ADDR, address(executor), amountIn);
|
||||
uint256 balanceBefore = WETH.balanceOf(trader);
|
||||
deal(WETH_ADDR, address(executor), amountIn);
|
||||
uint256 balanceBefore = USDC.balanceOf(trader);
|
||||
|
||||
vm.prank(trader);
|
||||
uint256 amountOut = executor.swap(amountIn, encodedQuote);
|
||||
|
||||
uint256 balanceAfter = WETH.balanceOf(trader);
|
||||
uint256 balanceAfter = USDC.balanceOf(trader);
|
||||
assertGt(balanceAfter, balanceBefore);
|
||||
assertEq(balanceAfter - balanceBefore, amountOut);
|
||||
assertEq(amountOut, quote.quoteTokenAmount);
|
||||
}
|
||||
|
||||
function testSwapRouterAmountUnderQuoteAmount() public {
|
||||
address trader = address(executor);
|
||||
address trader = address(ALICE);
|
||||
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
|
||||
uint256 amountIn = quote.baseTokenAmount - 1;
|
||||
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
|
||||
|
||||
deal(USDC_ADDR, address(executor), amountIn);
|
||||
uint256 balanceBefore = WETH.balanceOf(trader);
|
||||
deal(WETH_ADDR, address(executor), amountIn);
|
||||
uint256 balanceBefore = USDC.balanceOf(trader);
|
||||
|
||||
vm.prank(trader);
|
||||
uint256 amountOut = executor.swap(amountIn, encodedQuote);
|
||||
|
||||
uint256 balanceAfter = WETH.balanceOf(trader);
|
||||
uint256 balanceAfter = USDC.balanceOf(trader);
|
||||
assertGt(balanceAfter, balanceBefore);
|
||||
assertEq(balanceAfter - balanceBefore, amountOut);
|
||||
assertLt(amountOut, quote.quoteTokenAmount);
|
||||
}
|
||||
|
||||
function testSwapRouterAmountOverQuoteAmount() public {
|
||||
address trader = address(executor);
|
||||
address trader = address(ALICE);
|
||||
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
|
||||
uint256 amountIn = quote.baseTokenAmount + 1;
|
||||
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
|
||||
|
||||
deal(USDC_ADDR, address(executor), amountIn);
|
||||
uint256 balanceBefore = WETH.balanceOf(trader);
|
||||
deal(WETH_ADDR, address(executor), amountIn);
|
||||
uint256 balanceBefore = USDC.balanceOf(trader);
|
||||
|
||||
vm.prank(trader);
|
||||
uint256 amountOut = executor.swap(amountIn, encodedQuote);
|
||||
|
||||
uint256 balanceAfter = WETH.balanceOf(trader);
|
||||
uint256 balanceAfter = USDC.balanceOf(trader);
|
||||
assertGt(balanceAfter, balanceBefore);
|
||||
assertEq(balanceAfter - balanceBefore, amountOut);
|
||||
assertEq(amountOut, quote.quoteTokenAmount);
|
||||
@@ -182,23 +182,23 @@ contract HashflowExecutorECR20Test is Constants, HashflowUtils {
|
||||
returns (IHashflowRouter.RFQTQuote memory)
|
||||
{
|
||||
return IHashflowRouter.RFQTQuote({
|
||||
pool: address(0x4cE18FD7b44F40Aebd6911362d3AC25F14D5007f),
|
||||
externalAccount: address(0x50C03775C8E5b6227F1E00C4b3e479b4A7C57983),
|
||||
trader: address(executor),
|
||||
pool: address(0x5d8853028fbF6a2da43c7A828cc5f691E9456B44),
|
||||
externalAccount: address(0x9bA0CF1588E1DFA905eC948F7FE5104dD40EDa31),
|
||||
trader: address(ALICE),
|
||||
effectiveTrader: address(ALICE),
|
||||
baseToken: USDC_ADDR,
|
||||
quoteToken: WETH_ADDR,
|
||||
baseToken: WETH_ADDR,
|
||||
quoteToken: USDC_ADDR,
|
||||
effectiveBaseTokenAmount: 0,
|
||||
baseTokenAmount: 100,
|
||||
quoteTokenAmount: 23224549208,
|
||||
quoteExpiry: 1755001853,
|
||||
nonce: 1755001793084,
|
||||
baseTokenAmount: 1000000000000000000,
|
||||
quoteTokenAmount: 4286117034,
|
||||
quoteExpiry: 1755766775,
|
||||
nonce: 1755766744988,
|
||||
txid: bytes32(
|
||||
uint256(
|
||||
0x12500006400064000000174813b960ffffffffffffff00293fdb4569fe760000
|
||||
0x12500006400064000186078c183380ffffffffffffff00296d737ff6ae950000
|
||||
)
|
||||
),
|
||||
signature: hex"5b26977fecaf794c3d6900b9523b9632b5c62623f92732347dc9f24d8b5c4d611f5d733bbe82b594b6b47ab8aa1923c9f6b8aa66ef822ce412a767200f1520e11b"
|
||||
signature: hex"649d31cd74f1b11b4a3b32bd38c2525d78ce8f23bc2eaf7700899c3a396d3a137c861737dc780fa154699eafb3108a34cbb2d4e31a6f0623c169cc19e0fa296a1c"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -213,13 +213,13 @@ contract HashflowExecutorNativeTest is Constants, HashflowUtils {
|
||||
IERC20 USDC = IERC20(USDC_ADDR);
|
||||
|
||||
function setUp() public {
|
||||
forkBlock = 23125321; // Using expiry date: 1755006017, Native
|
||||
forkBlock = 23188504; // Using expiry date: 1755767859, Native
|
||||
vm.createSelectFork("mainnet", forkBlock);
|
||||
executor = new HashflowExecutorExposed(PERMIT2_ADDRESS);
|
||||
executor = new HashflowExecutorExposed(HASHFLOW_ROUTER, PERMIT2_ADDRESS);
|
||||
}
|
||||
|
||||
function testSwapNoSlippage() public {
|
||||
address trader = address(executor);
|
||||
address trader = address(ALICE);
|
||||
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
|
||||
uint256 amountIn = quote.baseTokenAmount;
|
||||
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
|
||||
@@ -242,29 +242,31 @@ contract HashflowExecutorNativeTest is Constants, HashflowUtils {
|
||||
returns (IHashflowRouter.RFQTQuote memory)
|
||||
{
|
||||
return IHashflowRouter.RFQTQuote({
|
||||
pool: address(0x51199bE500A8c59262478b621B1096F17638dc6F),
|
||||
externalAccount: address(0xCe79b081c0c924cb67848723ed3057234d10FC6b),
|
||||
trader: address(executor),
|
||||
pool: address(0x713DC4Df480235dBe2fB766E7120Cbd4041Dcb58),
|
||||
externalAccount: address(0x111BB8c3542F2B92fb41B8d913c01D3788431111),
|
||||
trader: address(ALICE),
|
||||
effectiveTrader: address(ALICE),
|
||||
baseToken: address(0x0000000000000000000000000000000000000000),
|
||||
quoteToken: USDC_ADDR,
|
||||
effectiveBaseTokenAmount: 0,
|
||||
baseTokenAmount: 10000000000000000,
|
||||
quoteTokenAmount: 43930745,
|
||||
quoteExpiry: 1755006017,
|
||||
nonce: 1755005977455,
|
||||
quoteTokenAmount: 42586008,
|
||||
quoteExpiry: 1755767859,
|
||||
nonce: 1755767819299,
|
||||
txid: bytes32(
|
||||
uint256(
|
||||
0x1250000640006400019071ef777818ffffffffffffff0029401b1bc51da00000
|
||||
0x1250000640006400018380fd594810ffffffffffffff00296d83e467cddd0000
|
||||
)
|
||||
),
|
||||
signature: hex"4c3554c928e4b15cd53d1047aee69a66103effa5107047b84949e48460b6978f25da9ad5b9ed31aa9ab2130e597fabea872f14b8c1b166ea079413cbaf2f4b4c1c"
|
||||
signature: hex"63c1c9c7d6902d1d4d2ae82777015433ef08366dde1c579a8c4cbc01059166064246f61f15b2cb130be8f2b28ea40d2c3586ef0133647fefa30003e70ffbd6131b"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
contract HashflowExecutorExposed is HashflowExecutor {
|
||||
constructor(address _permit2) HashflowExecutor(_permit2) {}
|
||||
constructor(address _hashflowRouter, address _permit2)
|
||||
HashflowExecutor(_hashflowRouter, _permit2)
|
||||
{}
|
||||
|
||||
function decodeData(bytes calldata data)
|
||||
external
|
||||
@@ -278,3 +280,37 @@ contract HashflowExecutorExposed is HashflowExecutor {
|
||||
return _decodeData(data);
|
||||
}
|
||||
}
|
||||
|
||||
contract TychoRouterSingleSwapTestForHashflow is TychoRouterTestSetup {
|
||||
function getForkBlock() public pure override returns (uint256) {
|
||||
return 23175437;
|
||||
}
|
||||
|
||||
function testHashflowIntegration() public {
|
||||
// Performs a swap from USDC to WBTC using Hashflow RFQ
|
||||
//
|
||||
// USDC ───(Hashflow RFQ)──> WBTC
|
||||
|
||||
// The Hashflow order expects:
|
||||
// - 4308094737 USDC input -> 3714751 WBTC output
|
||||
|
||||
uint256 amountIn = 4308094737;
|
||||
uint256 expectedAmountOut = 3714751;
|
||||
deal(USDC_ADDR, ALICE, amountIn);
|
||||
uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_single_encoding_strategy_hashflow");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, expectedAmountOut);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,6 @@ pub struct Permit2 {
|
||||
address: Address,
|
||||
client: EVMProvider,
|
||||
runtime_handle: Handle,
|
||||
// Store the runtime to prevent it from being dropped before use.
|
||||
// This is required since tycho-execution does not have a pre-existing runtime.
|
||||
// However, if the library is used in a context where a runtime already exists, it is not
|
||||
// necessary to store it.
|
||||
#[allow(dead_code)]
|
||||
runtime: Option<Arc<Runtime>>,
|
||||
}
|
||||
|
||||
@@ -23,10 +23,6 @@ use crate::encoding::{
|
||||
pub struct ProtocolApprovalsManager {
|
||||
client: EVMProvider,
|
||||
runtime_handle: Handle,
|
||||
// Store the runtime to prevent it from being dropped before use.
|
||||
// This is required since tycho-execution does not have a pre-existing runtime.
|
||||
// However, if the library is used in a context where a runtime already exists, it is not
|
||||
// necessary to store it.
|
||||
#[allow(dead_code)]
|
||||
runtime: Option<Arc<Runtime>>,
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::swap_encoder::swap_encoders::{
|
||||
BalancerV2SwapEncoder, BalancerV3SwapEncoder, BebopSwapEncoder, CurveSwapEncoder,
|
||||
EkuboSwapEncoder, MaverickV2SwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder,
|
||||
UniswapV4SwapEncoder,
|
||||
EkuboSwapEncoder, HashflowSwapEncoder, MaverickV2SwapEncoder, UniswapV2SwapEncoder,
|
||||
UniswapV3SwapEncoder, UniswapV4SwapEncoder,
|
||||
},
|
||||
swap_encoder::SwapEncoder,
|
||||
};
|
||||
@@ -91,6 +91,11 @@ impl SwapEncoderBuilder {
|
||||
"rfq:bebop" => {
|
||||
Ok(Box::new(BebopSwapEncoder::new(self.executor_address, self.chain, self.config)?))
|
||||
}
|
||||
"rfq:hashflow" => Ok(Box::new(HashflowSwapEncoder::new(
|
||||
self.executor_address,
|
||||
self.chain,
|
||||
self.config,
|
||||
)?)),
|
||||
_ => Err(EncodingError::FatalError(format!(
|
||||
"Unknown protocol system: {}",
|
||||
self.protocol_system
|
||||
|
||||
@@ -664,10 +664,6 @@ pub struct BebopSwapEncoder {
|
||||
native_token_bebop_address: Bytes,
|
||||
native_token_address: Bytes,
|
||||
runtime_handle: Handle,
|
||||
// Store the runtime to prevent it from being dropped before use.
|
||||
// This is required since tycho-execution does not have a pre-existing runtime.
|
||||
// However, if the library is used in a context where a runtime already exists, it is not
|
||||
// necessary to store it.
|
||||
#[allow(dead_code)]
|
||||
runtime: Option<Arc<Runtime>>,
|
||||
}
|
||||
@@ -715,28 +711,24 @@ impl SwapEncoder for BebopSwapEncoder {
|
||||
) -> 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 sender = encoding_context
|
||||
.router_address
|
||||
.clone()
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"The router address is needed to perform a Hashflow swap".to_string(),
|
||||
))?;
|
||||
let approval_needed = if swap.token_in == self.native_token_address {
|
||||
false
|
||||
} else {
|
||||
let tycho_router_address = bytes_to_address(&sender)?;
|
||||
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 swap.token_in == self.native_token_address {
|
||||
approval_needed = false;
|
||||
} else {
|
||||
approval_needed = token_approvals_manager.approval_needed(
|
||||
token_in,
|
||||
tycho_router_address,
|
||||
settlement_address,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
approval_needed = true;
|
||||
}
|
||||
ProtocolApprovalsManager::new()?.approval_needed(
|
||||
token_in,
|
||||
tycho_router_address,
|
||||
settlement_address,
|
||||
)?
|
||||
};
|
||||
|
||||
let (partial_fill_offset, original_filled_taker_amount, bebop_calldata) =
|
||||
if let Some(state) = &swap.protocol_state {
|
||||
@@ -850,6 +842,148 @@ impl SwapEncoder for BebopSwapEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HashflowSwapEncoder {
|
||||
executor_address: String,
|
||||
hashflow_router_address: String,
|
||||
native_token_address: Bytes,
|
||||
runtime_handle: Handle,
|
||||
#[allow(dead_code)]
|
||||
runtime: Option<Arc<Runtime>>,
|
||||
}
|
||||
|
||||
impl SwapEncoder for HashflowSwapEncoder {
|
||||
fn new(
|
||||
executor_address: String,
|
||||
chain: Chain,
|
||||
config: Option<HashMap<String, String>>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
let config = config.ok_or(EncodingError::FatalError(
|
||||
"Missing hashflow specific addresses in config".to_string(),
|
||||
))?;
|
||||
let hashflow_router_address = config
|
||||
.get("hashflow_router_address")
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"Missing hashflow router address in config".to_string(),
|
||||
))?
|
||||
.to_string();
|
||||
let native_token_address = chain.native_token().address;
|
||||
let (runtime_handle, runtime) = get_runtime()?;
|
||||
Ok(Self {
|
||||
executor_address,
|
||||
hashflow_router_address,
|
||||
native_token_address,
|
||||
runtime_handle,
|
||||
runtime,
|
||||
})
|
||||
}
|
||||
|
||||
fn encode_swap(
|
||||
&self,
|
||||
swap: &Swap,
|
||||
encoding_context: &EncodingContext,
|
||||
) -> Result<Vec<u8>, EncodingError> {
|
||||
// Native tokens doesn't need approval, only ERC20 tokens do
|
||||
let sender = encoding_context
|
||||
.router_address
|
||||
.clone()
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"The router address is needed to perform a Hashflow swap".to_string(),
|
||||
))?;
|
||||
|
||||
// Native ETH doesn't need approval, only ERC20 tokens do
|
||||
let approval_needed = if swap.token_in == self.native_token_address {
|
||||
false
|
||||
} else {
|
||||
let tycho_router_address = bytes_to_address(&sender)?;
|
||||
let hashflow_router_address = Address::from_str(&self.hashflow_router_address)
|
||||
.map_err(|_| {
|
||||
EncodingError::FatalError("Invalid hashflow router address address".to_string())
|
||||
})?;
|
||||
ProtocolApprovalsManager::new()?.approval_needed(
|
||||
bytes_to_address(&swap.token_in)?,
|
||||
tycho_router_address,
|
||||
hashflow_router_address,
|
||||
)?
|
||||
};
|
||||
|
||||
// Get quote
|
||||
let protocol_state = swap
|
||||
.protocol_state
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
EncodingError::FatalError("protocol_state is required for Hashflow".to_string())
|
||||
})?;
|
||||
let amount_in = swap
|
||||
.estimated_amount_in
|
||||
.as_ref()
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"Estimated amount in is mandatory for a Hashflow swap".to_string(),
|
||||
))?
|
||||
.clone();
|
||||
let sender = encoding_context
|
||||
.router_address
|
||||
.clone()
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"The router address is needed to perform a Hashflow swap".to_string(),
|
||||
))?;
|
||||
let signed_quote = block_in_place(|| {
|
||||
self.runtime_handle.block_on(async {
|
||||
protocol_state
|
||||
.as_indicatively_priced()?
|
||||
.request_signed_quote(GetAmountOutParams {
|
||||
amount_in,
|
||||
token_in: swap.token_in.clone(),
|
||||
token_out: swap.token_out.clone(),
|
||||
sender,
|
||||
receiver: encoding_context.receiver.clone(),
|
||||
})
|
||||
.await
|
||||
})
|
||||
})?;
|
||||
|
||||
// Encode packed data for the executor
|
||||
// Format: approval_needed | transfer_type | hashflow_calldata[..]
|
||||
let hashflow_fields = [
|
||||
"pool",
|
||||
"external_account",
|
||||
"trader",
|
||||
"base_token",
|
||||
"quote_token",
|
||||
"base_token_amount",
|
||||
"quote_token_amount",
|
||||
"quote_expiry",
|
||||
"nonce",
|
||||
"tx_id",
|
||||
"signature",
|
||||
];
|
||||
let mut hashflow_calldata = vec![];
|
||||
for field in &hashflow_fields {
|
||||
let value = signed_quote
|
||||
.quote_attributes
|
||||
.get(*field)
|
||||
.ok_or(EncodingError::FatalError(format!(
|
||||
"Hashflow quote must have a {field} attribute"
|
||||
)))?;
|
||||
hashflow_calldata.extend_from_slice(value);
|
||||
}
|
||||
let args = (
|
||||
(encoding_context.transfer_type as u8).to_be_bytes(),
|
||||
(approval_needed as u8).to_be_bytes(),
|
||||
&hashflow_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())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
@@ -1985,8 +2119,17 @@ mod tests {
|
||||
};
|
||||
let bebop_state = MockRFQState {
|
||||
quote_amount_out,
|
||||
quote_calldata: bebop_calldata.clone(),
|
||||
quote_partial_fill_offset: partial_fill_offset,
|
||||
quote_data: HashMap::from([
|
||||
("calldata".to_string(), bebop_calldata.clone()),
|
||||
(
|
||||
"partial_fill_offset".to_string(),
|
||||
Bytes::from(
|
||||
partial_fill_offset
|
||||
.to_be_bytes()
|
||||
.to_vec(),
|
||||
),
|
||||
),
|
||||
]),
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
@@ -2037,4 +2180,159 @@ mod tests {
|
||||
assert_eq!(hex_swap, expected_swap + &bebop_calldata.to_string()[2..]);
|
||||
}
|
||||
}
|
||||
|
||||
mod hashflow {
|
||||
use alloy::hex::encode;
|
||||
use num_bigint::BigUint;
|
||||
|
||||
use super::*;
|
||||
use crate::encoding::{
|
||||
evm::testing_utils::MockRFQState,
|
||||
models::{SwapBuilder, TransferType},
|
||||
};
|
||||
|
||||
fn hashflow_config() -> Option<HashMap<String, String>> {
|
||||
Some(HashMap::from([(
|
||||
"hashflow_router_address".to_string(),
|
||||
"0x55084eE0fEf03f14a305cd24286359A35D735151".to_string(),
|
||||
)]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_hashflow_single_fails_without_protocol_data() {
|
||||
// Hashflow requires a swap with protocol data, otherwise will return an error
|
||||
let hashflow_component = ProtocolComponent {
|
||||
id: String::from("hashflow-rfq"),
|
||||
protocol_system: String::from("rfq:hashflow"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); // WETH
|
||||
|
||||
let swap = SwapBuilder::new(hashflow_component, token_in.clone(), token_out.clone())
|
||||
.estimated_amount_in(BigUint::from_str("3000000000").unwrap())
|
||||
.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 = HashflowSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
Chain::Ethereum,
|
||||
hashflow_config(),
|
||||
)
|
||||
.unwrap();
|
||||
encoder
|
||||
.encode_swap(&swap, &encoding_context)
|
||||
.expect_err("Should returned an error if the swap has no protocol state");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_hashflow_single_with_protocol_state() {
|
||||
// 3000 USDC -> 1 WETH using a mocked RFQ state to get a quote
|
||||
let quote_amount_out = BigUint::from_str("1000000000000000000").unwrap();
|
||||
|
||||
let hashflow_component = ProtocolComponent {
|
||||
id: String::from("hashflow-rfq"),
|
||||
protocol_system: String::from("rfq:hashflow"),
|
||||
..Default::default()
|
||||
};
|
||||
let hashflow_quote_data = vec![
|
||||
(
|
||||
"pool".to_string(),
|
||||
Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(),
|
||||
),
|
||||
(
|
||||
"external_account".to_string(),
|
||||
Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(),
|
||||
),
|
||||
(
|
||||
"trader".to_string(),
|
||||
Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(),
|
||||
),
|
||||
(
|
||||
"base_token".to_string(),
|
||||
Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
|
||||
),
|
||||
(
|
||||
"quote_token".to_string(),
|
||||
Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(),
|
||||
),
|
||||
(
|
||||
"base_token_amount".to_string(),
|
||||
Bytes::from(biguint_to_u256(&BigUint::from(3000_u64)).to_be_bytes::<32>().to_vec()),
|
||||
),
|
||||
(
|
||||
"quote_token_amount".to_string(),
|
||||
Bytes::from(biguint_to_u256(&BigUint::from(1_u64)).to_be_bytes::<32>().to_vec()),
|
||||
),
|
||||
("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())),
|
||||
("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())),
|
||||
(
|
||||
"tx_id".to_string(),
|
||||
Bytes::from_str(
|
||||
"0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000",
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()),
|
||||
];
|
||||
let hashflow_quote_data_values =
|
||||
hashflow_quote_data
|
||||
.iter()
|
||||
.fold(vec![], |mut acc, (_key, value)| {
|
||||
acc.extend_from_slice(value);
|
||||
acc
|
||||
});
|
||||
let hashflow_calldata = Bytes::from(hashflow_quote_data_values);
|
||||
let hashflow_state = MockRFQState {
|
||||
quote_amount_out,
|
||||
quote_data: hashflow_quote_data
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); // WETH
|
||||
|
||||
let swap = SwapBuilder::new(hashflow_component, token_in.clone(), token_out.clone())
|
||||
.estimated_amount_in(BigUint::from_str("3000000000").unwrap())
|
||||
.protocol_state(&hashflow_state)
|
||||
.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 = HashflowSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
Chain::Ethereum,
|
||||
hashflow_config(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoded_swap = encoder
|
||||
.encode_swap(&swap, &encoding_context)
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
let expected_swap = String::from(concat!(
|
||||
"01", // transfer type
|
||||
"01", // approval needed
|
||||
));
|
||||
assert_eq!(hex_swap, expected_swap + &hashflow_calldata.to_string()[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ use tycho_common::{
|
||||
#[derive(Debug)]
|
||||
pub struct MockRFQState {
|
||||
pub quote_amount_out: BigUint,
|
||||
pub quote_calldata: Bytes,
|
||||
pub quote_partial_fill_offset: u64,
|
||||
pub quote_data: HashMap<String, Bytes>,
|
||||
}
|
||||
impl ProtocolSim for MockRFQState {
|
||||
fn fee(&self) -> f64 {
|
||||
@@ -82,23 +81,12 @@ impl IndicativelyPriced for MockRFQState {
|
||||
&self,
|
||||
params: GetAmountOutParams,
|
||||
) -> Result<SignedQuote, SimulationError> {
|
||||
let mut quote_attributes: HashMap<String, Bytes> = HashMap::new();
|
||||
quote_attributes.insert("calldata".to_string(), self.quote_calldata.clone());
|
||||
quote_attributes.insert(
|
||||
"partial_fill_offset".to_string(),
|
||||
Bytes::from(
|
||||
self.quote_partial_fill_offset
|
||||
.to_be_bytes()
|
||||
.to_vec(),
|
||||
),
|
||||
);
|
||||
|
||||
Ok(SignedQuote {
|
||||
base_token: params.token_in,
|
||||
quote_token: params.token_out,
|
||||
amount_in: params.amount_in,
|
||||
amount_out: self.quote_amount_out.clone(),
|
||||
quote_attributes,
|
||||
quote_attributes: self.quote_data.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,9 @@ pub fn get_static_attribute(swap: &Swap, attribute_name: &str) -> Result<Vec<u8>
|
||||
.to_vec())
|
||||
}
|
||||
|
||||
/// Returns the current Tokio runtime handle, or creates a new one if it doesn't exist.
|
||||
/// It also returns the runtime to prevent it from being dropped before use.
|
||||
/// This is required since tycho-execution does not have a pre-existing runtime.
|
||||
pub fn get_runtime() -> Result<(Handle, Option<Arc<Runtime>>), EncodingError> {
|
||||
match Handle::try_current() {
|
||||
Ok(h) => Ok((h, None)),
|
||||
|
||||
@@ -21,6 +21,10 @@ pub fn bob_address() -> Bytes {
|
||||
Bytes::from_str("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e").unwrap()
|
||||
}
|
||||
|
||||
pub fn alice_address() -> Bytes {
|
||||
Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap()
|
||||
}
|
||||
|
||||
pub fn eth_chain() -> Chain {
|
||||
Chain::Ethereum
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ use alloy::hex::encode;
|
||||
use num_bigint::{BigInt, BigUint};
|
||||
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||
use tycho_execution::encoding::{
|
||||
evm::{testing_utils::MockRFQState, utils::write_calldata_to_file},
|
||||
evm::{
|
||||
testing_utils::MockRFQState,
|
||||
utils::{biguint_to_u256, write_calldata_to_file},
|
||||
},
|
||||
models::{Solution, Swap, SwapBuilder, UserTransferType},
|
||||
};
|
||||
|
||||
use crate::common::{
|
||||
bob_address, encoding::encode_tycho_router_call, eth, eth_chain, get_signer,
|
||||
alice_address, bob_address, encoding::encode_tycho_router_call, eth, eth_chain, get_signer,
|
||||
get_tycho_router_encoder, usdc, wbtc, weth,
|
||||
};
|
||||
|
||||
@@ -650,8 +653,17 @@ fn test_uniswap_v3_bebop() {
|
||||
|
||||
let bebop_state = MockRFQState {
|
||||
quote_amount_out,
|
||||
quote_calldata: bebop_calldata.clone(),
|
||||
quote_partial_fill_offset: partial_fill_offset,
|
||||
quote_data: HashMap::from([
|
||||
("calldata".to_string(), bebop_calldata),
|
||||
(
|
||||
"partial_fill_offset".to_string(),
|
||||
Bytes::from(
|
||||
partial_fill_offset
|
||||
.to_be_bytes()
|
||||
.to_vec(),
|
||||
),
|
||||
),
|
||||
]),
|
||||
};
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
@@ -698,3 +710,129 @@ fn test_uniswap_v3_bebop() {
|
||||
let hex_calldata = encode(&calldata);
|
||||
write_calldata_to_file("test_uniswap_v3_bebop", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_uniswap_v3_hashflow() {
|
||||
// Note: This test does not assert anything. It is only used to obtain
|
||||
// integration test data for our router solidity test.
|
||||
//
|
||||
// Performs a sequential swap from WETH to WBTC through USDC using USV3 and
|
||||
// Hashflow RFQ
|
||||
//
|
||||
// WETH ───(USV3)──> USDC ───(Hashflow RFQ)──> WBTC
|
||||
|
||||
let weth = weth();
|
||||
let usdc = usdc();
|
||||
let wbtc = wbtc();
|
||||
|
||||
// First swap: WETH -> USDC via UniswapV3
|
||||
let swap_weth_usdc = SwapBuilder::new(
|
||||
ProtocolComponent {
|
||||
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* WETH-USDC USV3 Pool
|
||||
* 0.05% */
|
||||
protocol_system: "uniswap_v3".to_string(),
|
||||
static_attributes: {
|
||||
let mut attrs = HashMap::new();
|
||||
attrs
|
||||
.insert("fee".to_string(), Bytes::from(BigInt::from(500).to_signed_bytes_be()));
|
||||
attrs
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
weth.clone(),
|
||||
usdc.clone(),
|
||||
)
|
||||
.build();
|
||||
|
||||
// Second swap: USDC -> WBTC via Hashflow RFQ using real order data
|
||||
let quote_amount_out = BigUint::from_str("3714751").unwrap();
|
||||
|
||||
let hashflow_state = MockRFQState {
|
||||
quote_amount_out,
|
||||
quote_data: HashMap::from([
|
||||
(
|
||||
"pool".to_string(),
|
||||
Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(),
|
||||
),
|
||||
(
|
||||
"external_account".to_string(),
|
||||
Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(),
|
||||
),
|
||||
(
|
||||
"trader".to_string(),
|
||||
Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(),
|
||||
),
|
||||
(
|
||||
"base_token".to_string(),
|
||||
Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
|
||||
),
|
||||
(
|
||||
"quote_token".to_string(),
|
||||
Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(),
|
||||
),
|
||||
(
|
||||
"base_token_amount".to_string(),
|
||||
Bytes::from(biguint_to_u256(&BigUint::from(4308094737_u64)).to_be_bytes::<32>().to_vec()),
|
||||
),
|
||||
(
|
||||
"quote_token_amount".to_string(),
|
||||
Bytes::from(biguint_to_u256(&BigUint::from(3714751_u64)).to_be_bytes::<32>().to_vec()),
|
||||
),
|
||||
("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())),
|
||||
("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())),
|
||||
(
|
||||
"tx_id".to_string(),
|
||||
Bytes::from_str(
|
||||
"0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000",
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()),
|
||||
]),
|
||||
};
|
||||
|
||||
let hashflow_component = ProtocolComponent {
|
||||
id: String::from("hashflow-rfq"),
|
||||
protocol_system: String::from("rfq:hashflow"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let swap_usdc_wbtc = SwapBuilder::new(hashflow_component, usdc.clone(), wbtc.clone())
|
||||
.estimated_amount_in(BigUint::from_str("4308094737").unwrap())
|
||||
.protocol_state(&hashflow_state)
|
||||
.build();
|
||||
|
||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: weth,
|
||||
given_amount: BigUint::from_str("1000000000000000000").unwrap(),
|
||||
checked_token: wbtc,
|
||||
checked_amount: BigUint::from_str("3714751").unwrap(),
|
||||
sender: alice_address(),
|
||||
receiver: alice_address(),
|
||||
swaps: vec![swap_weth_usdc, swap_usdc_wbtc],
|
||||
..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 = encode(&calldata);
|
||||
write_calldata_to_file("test_uniswap_v3_hashflow", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
@@ -5,13 +5,16 @@ use alloy::{hex, hex::encode};
|
||||
use num_bigint::{BigInt, BigUint};
|
||||
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||
use tycho_execution::encoding::{
|
||||
evm::utils::{biguint_to_u256, write_calldata_to_file},
|
||||
evm::{
|
||||
testing_utils::MockRFQState,
|
||||
utils::{biguint_to_u256, write_calldata_to_file},
|
||||
},
|
||||
models::{Solution, Swap, SwapBuilder, UserTransferType},
|
||||
};
|
||||
|
||||
use crate::common::{
|
||||
build_bebop_calldata, encoding::encode_tycho_router_call, eth, eth_chain, get_signer,
|
||||
get_tycho_router_encoder, ondo, pepe, usdc, weth,
|
||||
alice_address, build_bebop_calldata, encoding::encode_tycho_router_call, eth, eth_chain,
|
||||
get_signer, get_tycho_router_encoder, ondo, pepe, usdc, wbtc, weth,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -714,3 +717,107 @@ fn test_single_encoding_strategy_bebop_aggregate() {
|
||||
|
||||
write_calldata_to_file("test_single_encoding_strategy_bebop_aggregate", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_hashflow() {
|
||||
// Note: This test does not assert anything. It is only used to obtain
|
||||
// integration test data for our router solidity test.
|
||||
//
|
||||
// Performs a swap from USDC to WBTC using Hashflow RFQ
|
||||
//
|
||||
// USDC ───(Hashflow RFQ)──> WBTC
|
||||
|
||||
let usdc = usdc();
|
||||
let wbtc = wbtc();
|
||||
|
||||
// USDC -> WBTC via Hashflow RFQ using real order data
|
||||
let quote_amount_out = BigUint::from_str("3714751").unwrap();
|
||||
|
||||
let hashflow_state = MockRFQState {
|
||||
quote_amount_out,
|
||||
quote_data: HashMap::from([
|
||||
(
|
||||
"pool".to_string(),
|
||||
Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(),
|
||||
),
|
||||
(
|
||||
"external_account".to_string(),
|
||||
Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(),
|
||||
),
|
||||
(
|
||||
"trader".to_string(),
|
||||
Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(),
|
||||
),
|
||||
(
|
||||
"base_token".to_string(),
|
||||
Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
|
||||
),
|
||||
(
|
||||
"quote_token".to_string(),
|
||||
Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(),
|
||||
),
|
||||
(
|
||||
"base_token_amount".to_string(),
|
||||
Bytes::from(biguint_to_u256(&BigUint::from(4308094737_u64)).to_be_bytes::<32>().to_vec()),
|
||||
),
|
||||
(
|
||||
"quote_token_amount".to_string(),
|
||||
Bytes::from(biguint_to_u256(&BigUint::from(3714751_u64)).to_be_bytes::<32>().to_vec()),
|
||||
),
|
||||
("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())),
|
||||
("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())),
|
||||
(
|
||||
"tx_id".to_string(),
|
||||
Bytes::from_str(
|
||||
"0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000",
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()),
|
||||
]),
|
||||
};
|
||||
|
||||
let hashflow_component = ProtocolComponent {
|
||||
id: String::from("hashflow-rfq"),
|
||||
protocol_system: String::from("rfq:hashflow"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let swap_usdc_wbtc = SwapBuilder::new(hashflow_component, usdc.clone(), wbtc.clone())
|
||||
.estimated_amount_in(BigUint::from_str("4308094737").unwrap())
|
||||
.protocol_state(&hashflow_state)
|
||||
.build();
|
||||
|
||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: usdc,
|
||||
given_amount: BigUint::from_str("4308094737").unwrap(),
|
||||
checked_token: wbtc,
|
||||
checked_amount: BigUint::from_str("3714751").unwrap(),
|
||||
sender: alice_address(),
|
||||
receiver: alice_address(),
|
||||
swaps: vec![swap_usdc_wbtc],
|
||||
..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 = encode(&calldata);
|
||||
write_calldata_to_file("test_single_encoding_strategy_hashflow", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user