Merge branch 'main' into encoding/tnl/derive-clone
This commit is contained in:
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,3 +1,19 @@
|
||||
## [0.85.0](https://github.com/propeller-heads/tycho-execution/compare/0.84.0...0.85.0) (2025-05-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add mav executor ([0ac722d](https://github.com/propeller-heads/tycho-execution/commit/0ac722d91f6c2f7c09531f5f56f057d1fe014c6f))
|
||||
* add swap encode ([72a651d](https://github.com/propeller-heads/tycho-execution/commit/72a651d453943d554722d0fa4818255fbec01ce5))
|
||||
* Transfer Optimizations in MaverickV2 ([bcef8f6](https://github.com/propeller-heads/tycho-execution/commit/bcef8f69f628c3a37a7cb5462e26200ad5aab1ee))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add maverick for build ([bab30e3](https://github.com/propeller-heads/tycho-execution/commit/bab30e3958ee62091b322ac0bc540939c050ee9e))
|
||||
* maverick test fork block ([4c93830](https://github.com/propeller-heads/tycho-execution/commit/4c938306bda62d37b8932697f98d0d667a96532b))
|
||||
* swap test ([d103ca9](https://github.com/propeller-heads/tycho-execution/commit/d103ca9e33f931f18c8af79efa5d77d801bab2e9))
|
||||
|
||||
## [0.84.0](https://github.com/propeller-heads/tycho-execution/compare/0.83.0...0.84.0) (2025-04-28)
|
||||
|
||||
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4469,7 +4469,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tycho-execution"
|
||||
version = "0.84.0"
|
||||
version = "0.85.0"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"alloy-primitives",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tycho-execution"
|
||||
version = "0.84.0"
|
||||
version = "0.85.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"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"uniswap_v4": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a",
|
||||
"vm:balancer_v2": "0xc7183455a4C133Ae270771860664b6B7ec320bB1",
|
||||
"ekubo_v2": "0xa0Cb889707d426A7A386870A03bc70d1b0697598",
|
||||
"vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211"
|
||||
"vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211",
|
||||
"vm:maverick_v2": "0xA4AD4f68d0b91CFD19687c881e50f3A00242828c"
|
||||
}
|
||||
}
|
||||
|
||||
106
foundry/src/executors/MaverickV2Executor.sol
Normal file
106
foundry/src/executors/MaverickV2Executor.sol
Normal file
@@ -0,0 +1,106 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "@interfaces/IExecutor.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "./TokenTransfer.sol";
|
||||
|
||||
error MaverickV2Executor__InvalidDataLength();
|
||||
error MaverickV2Executor__InvalidTarget();
|
||||
error MaverickV2Executor__InvalidFactory();
|
||||
|
||||
contract MaverickV2Executor is IExecutor, TokenTransfer {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address public immutable factory;
|
||||
address private immutable self;
|
||||
|
||||
constructor(address _factory, address _permit2) TokenTransfer(_permit2) {
|
||||
if (_factory == address(0)) {
|
||||
revert MaverickV2Executor__InvalidFactory();
|
||||
}
|
||||
factory = _factory;
|
||||
self = address(this);
|
||||
}
|
||||
|
||||
// slither-disable-next-line locked-ether
|
||||
function swap(uint256 givenAmount, bytes calldata data)
|
||||
external
|
||||
payable
|
||||
returns (uint256 calculatedAmount)
|
||||
{
|
||||
address target;
|
||||
address receiver;
|
||||
IERC20 tokenIn;
|
||||
TransferType transferType;
|
||||
|
||||
(tokenIn, target, receiver, transferType) = _decodeData(data);
|
||||
|
||||
_verifyPairAddress(target);
|
||||
IMaverickV2Pool pool = IMaverickV2Pool(target);
|
||||
|
||||
bool isTokenAIn = pool.tokenA() == tokenIn;
|
||||
int32 tickLimit = isTokenAIn ? type(int32).max : type(int32).min;
|
||||
IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool
|
||||
.SwapParams({
|
||||
amount: givenAmount,
|
||||
tokenAIn: isTokenAIn,
|
||||
exactOutput: false,
|
||||
tickLimit: tickLimit
|
||||
});
|
||||
|
||||
_transfer(
|
||||
address(tokenIn), msg.sender, target, givenAmount, transferType
|
||||
);
|
||||
// slither-disable-next-line unused-return
|
||||
(, calculatedAmount) = pool.swap(receiver, swapParams, "");
|
||||
}
|
||||
|
||||
function _decodeData(bytes calldata data)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
IERC20 inToken,
|
||||
address target,
|
||||
address receiver,
|
||||
TransferType transferType
|
||||
)
|
||||
{
|
||||
if (data.length != 61) {
|
||||
revert MaverickV2Executor__InvalidDataLength();
|
||||
}
|
||||
inToken = IERC20(address(bytes20(data[0:20])));
|
||||
target = address(bytes20(data[20:40]));
|
||||
receiver = address(bytes20(data[40:60]));
|
||||
transferType = TransferType(uint8(data[60]));
|
||||
}
|
||||
|
||||
function _verifyPairAddress(address target) internal view {
|
||||
if (!IMaverickV2Factory(factory).isFactoryPool(IMaverickV2Pool(target)))
|
||||
{
|
||||
revert MaverickV2Executor__InvalidTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IMaverickV2Factory {
|
||||
function isFactoryPool(IMaverickV2Pool pool) external view returns (bool);
|
||||
}
|
||||
|
||||
interface IMaverickV2Pool {
|
||||
struct SwapParams {
|
||||
uint256 amount;
|
||||
bool tokenAIn;
|
||||
bool exactOutput;
|
||||
int32 tickLimit;
|
||||
}
|
||||
|
||||
function swap(
|
||||
address recipient,
|
||||
SwapParams memory params,
|
||||
bytes calldata data
|
||||
) external returns (uint256 amountIn, uint256 amountOut);
|
||||
|
||||
function tokenA() external view returns (IERC20);
|
||||
function tokenB() external view returns (IERC20);
|
||||
}
|
||||
@@ -54,6 +54,12 @@ contract Constants is Test, BaseConstants {
|
||||
address WSTTAO_ADDR = address(0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67);
|
||||
address WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44);
|
||||
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
|
||||
address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);
|
||||
|
||||
// Maverick v2
|
||||
address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e;
|
||||
address GHO_USDC_POOL = 0x14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67;
|
||||
|
||||
// Uniswap v2
|
||||
address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11;
|
||||
address DAI_USDC_POOL = 0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5;
|
||||
|
||||
@@ -186,6 +186,26 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
|
||||
assertEq(balanceAfter - balanceBefore, 1474406268748155809);
|
||||
}
|
||||
|
||||
function testSingleMaverickIntegration() public {
|
||||
vm.stopPrank();
|
||||
|
||||
deal(GHO_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(GHO_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_single_encoding_strategy_maverick");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertGe(balanceAfter - balanceBefore, 999725);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSingleEkuboIntegration() public {
|
||||
vm.stopPrank();
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
|
||||
import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol";
|
||||
import {Permit2TestHelper} from "./Permit2TestHelper.sol";
|
||||
import "./TestUtils.sol";
|
||||
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
|
||||
|
||||
contract TychoRouterExposed is TychoRouter {
|
||||
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {}
|
||||
@@ -53,6 +54,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
BalancerV2Executor public balancerv2Executor;
|
||||
EkuboExecutor public ekuboExecutor;
|
||||
CurveExecutor public curveExecutor;
|
||||
MaverickV2Executor public maverickv2Executor;
|
||||
MockERC20[] tokens;
|
||||
|
||||
function setUp() public {
|
||||
@@ -110,8 +112,10 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
balancerv2Executor = new BalancerV2Executor(PERMIT2_ADDRESS);
|
||||
ekuboExecutor = new EkuboExecutor(ekuboCore, PERMIT2_ADDRESS);
|
||||
curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS);
|
||||
maverickv2Executor =
|
||||
new MaverickV2Executor(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS);
|
||||
|
||||
address[] memory executors = new address[](7);
|
||||
address[] memory executors = new address[](8);
|
||||
executors[0] = address(usv2Executor);
|
||||
executors[1] = address(usv3Executor);
|
||||
executors[2] = address(pancakev3Executor);
|
||||
@@ -119,6 +123,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
executors[4] = address(balancerv2Executor);
|
||||
executors[5] = address(ekuboExecutor);
|
||||
executors[6] = address(curveExecutor);
|
||||
executors[7] = address(maverickv2Executor);
|
||||
return executors;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,3 +24,5 @@ test_encode_balancer_v2:c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754
|
||||
test_ekubo_encode_swap_multi:00ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032
|
||||
test_encode_uniswap_v4_sequential_swap:4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c
|
||||
test_encode_uniswap_v4_simple_swap:4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec70100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec7000064000001
|
||||
test_single_encoding_strategy_maverick:20144a070000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000051a4ad4f68d0b91cfd19687c881e50f3a00242828c40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f14cf6d2fe3e1b326114b07d22a6f6bb59e346c67cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc201000000000000000000000000000000
|
||||
test_encode_maverick_v2:40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f14cf6d2fe3e1b326114b07d22a6f6bb59e346c671d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00
|
||||
|
||||
128
foundry/test/executors/MaverickV2Executor.t.sol
Normal file
128
foundry/test/executors/MaverickV2Executor.t.sol
Normal file
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "@src/executors/MaverickV2Executor.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
import "../TestUtils.sol";
|
||||
|
||||
contract MaverickV2ExecutorExposed is MaverickV2Executor {
|
||||
constructor(address _factory, address _permit2)
|
||||
MaverickV2Executor(_factory, _permit2)
|
||||
{}
|
||||
|
||||
function decodeParams(bytes calldata data)
|
||||
external
|
||||
pure
|
||||
returns (
|
||||
IERC20 tokenIn,
|
||||
address target,
|
||||
address receiver,
|
||||
TransferType transferType
|
||||
)
|
||||
{
|
||||
return _decodeData(data);
|
||||
}
|
||||
}
|
||||
|
||||
contract MaverickV2ExecutorTest is TestUtils, Constants {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
MaverickV2ExecutorExposed maverickV2Exposed;
|
||||
IERC20 GHO = IERC20(GHO_ADDR);
|
||||
IERC20 USDC = IERC20(USDC_ADDR);
|
||||
|
||||
function setUp() public {
|
||||
uint256 forkBlock = 22096000;
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
maverickV2Exposed =
|
||||
new MaverickV2ExecutorExposed(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS);
|
||||
}
|
||||
|
||||
function testDecodeParams() public view {
|
||||
bytes memory params = abi.encodePacked(
|
||||
GHO_ADDR,
|
||||
GHO_USDC_POOL,
|
||||
address(2),
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
(
|
||||
IERC20 tokenIn,
|
||||
address target,
|
||||
address receiver,
|
||||
TokenTransfer.TransferType transferType
|
||||
) = maverickV2Exposed.decodeParams(params);
|
||||
|
||||
assertEq(address(tokenIn), GHO_ADDR);
|
||||
assertEq(target, GHO_USDC_POOL);
|
||||
assertEq(receiver, address(2));
|
||||
assertEq(
|
||||
uint8(transferType),
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
|
||||
);
|
||||
}
|
||||
|
||||
function testDecodeParamsInvalidDataLength() public {
|
||||
bytes memory invalidParams =
|
||||
abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, address(2));
|
||||
|
||||
vm.expectRevert(MaverickV2Executor__InvalidDataLength.selector);
|
||||
maverickV2Exposed.decodeParams(invalidParams);
|
||||
}
|
||||
|
||||
function testSwap() public {
|
||||
uint256 amountIn = 10e18;
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
GHO_ADDR,
|
||||
GHO_USDC_POOL,
|
||||
BOB,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
deal(GHO_ADDR, address(maverickV2Exposed), amountIn);
|
||||
uint256 balanceBefore = USDC.balanceOf(BOB);
|
||||
|
||||
uint256 amountOut = maverickV2Exposed.swap(amountIn, protocolData);
|
||||
|
||||
uint256 balanceAfter = USDC.balanceOf(BOB);
|
||||
assertGt(balanceAfter, balanceBefore);
|
||||
assertEq(balanceAfter - balanceBefore, amountOut);
|
||||
}
|
||||
|
||||
function testDecodeIntegration() public view {
|
||||
// Generated by the SwapEncoder - test_encode_maverick_v2
|
||||
bytes memory protocolData =
|
||||
loadCallDataFromFile("test_encode_maverick_v2");
|
||||
|
||||
(
|
||||
IERC20 tokenIn,
|
||||
address pool,
|
||||
address receiver,
|
||||
TokenTransfer.TransferType transferType
|
||||
) = maverickV2Exposed.decodeParams(protocolData);
|
||||
|
||||
assertEq(address(tokenIn), GHO_ADDR);
|
||||
assertEq(pool, GHO_USDC_POOL);
|
||||
assertEq(receiver, BOB);
|
||||
assertEq(
|
||||
uint8(transferType),
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
|
||||
);
|
||||
}
|
||||
|
||||
function testSwapIntegration() public {
|
||||
// Generated by the SwapEncoder - test_encode_maverick_v2
|
||||
bytes memory protocolData =
|
||||
loadCallDataFromFile("test_encode_maverick_v2");
|
||||
|
||||
uint256 amountIn = 10 ** 18;
|
||||
deal(GHO_ADDR, address(maverickV2Exposed), amountIn);
|
||||
uint256 balanceBefore = USDC.balanceOf(BOB);
|
||||
|
||||
uint256 amountOut = maverickV2Exposed.swap(amountIn, protocolData);
|
||||
|
||||
uint256 balanceAfter = USDC.balanceOf(BOB);
|
||||
assertGt(balanceAfter, balanceBefore);
|
||||
assertEq(balanceAfter - balanceBefore, amountOut);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ pub static GROUPABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> = LazyLock::new(
|
||||
|
||||
/// These protocols need an external in transfer to the pool. This transfer can be from the router,
|
||||
/// from the user or from the previous pool. Any protocols that are not defined here expect funds to
|
||||
/// be in the router at the time of swap and do the transfer themselves from msg.sender
|
||||
/// be in the router at the time of swap and do the transfer themselves from `msg.sender`
|
||||
pub static IN_TRANSFER_REQUIRED_PROTOCOLS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
|
||||
let mut set = HashSet::new();
|
||||
set.insert("uniswap_v2");
|
||||
@@ -30,6 +30,7 @@ pub static IN_TRANSFER_REQUIRED_PROTOCOLS: LazyLock<HashSet<&'static str>> = Laz
|
||||
set.insert("pancakeswap_v3");
|
||||
set.insert("uniswap_v4");
|
||||
set.insert("ekubo_v2");
|
||||
set.insert("vm:maverick_v2");
|
||||
set
|
||||
});
|
||||
|
||||
|
||||
@@ -2318,6 +2318,56 @@ mod tests {
|
||||
write_calldata_to_file("test_single_encoding_strategy_ekubo", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_maverick() {
|
||||
// GHO -> (maverick) -> USDC
|
||||
let maverick_pool = ProtocolComponent {
|
||||
id: String::from("0x14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67"),
|
||||
protocol_system: String::from("vm:maverick_v2"),
|
||||
..Default::default()
|
||||
};
|
||||
let token_in = Bytes::from("0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f");
|
||||
let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
|
||||
let swap = Swap {
|
||||
component: maverick_pool,
|
||||
token_in: token_in.clone(),
|
||||
token_out: token_out.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
|
||||
let swap_encoder_registry = get_swap_encoder_registry();
|
||||
let encoder = SingleSwapStrategyEncoder::new(
|
||||
eth_chain(),
|
||||
swap_encoder_registry,
|
||||
None,
|
||||
Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: token_in,
|
||||
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||
checked_token: token_out,
|
||||
expected_amount: None,
|
||||
checked_amount: Some(BigUint::from_str("1000").unwrap()),
|
||||
slippage: None,
|
||||
// Alice
|
||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
swaps: vec![swap],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (calldata, _) = encoder
|
||||
.encode_strategy(solution)
|
||||
.unwrap();
|
||||
|
||||
let hex_calldata = encode(&calldata);
|
||||
write_calldata_to_file("test_single_encoding_strategy_maverick", hex_calldata.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_usv4_eth_in() {
|
||||
// Performs a single swap from ETH to PEPE using a USV4 pool
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::collections::HashMap;
|
||||
use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::swap_encoder::swap_encoders::{
|
||||
BalancerV2SwapEncoder, CurveSwapEncoder, EkuboSwapEncoder, UniswapV2SwapEncoder,
|
||||
UniswapV3SwapEncoder, UniswapV4SwapEncoder,
|
||||
BalancerV2SwapEncoder, CurveSwapEncoder, EkuboSwapEncoder, MaverickV2SwapEncoder,
|
||||
UniswapV2SwapEncoder, UniswapV3SwapEncoder, UniswapV4SwapEncoder,
|
||||
},
|
||||
models::Chain,
|
||||
swap_encoder::SwapEncoder,
|
||||
@@ -76,6 +76,11 @@ impl SwapEncoderBuilder {
|
||||
"vm:curve" => {
|
||||
Ok(Box::new(CurveSwapEncoder::new(self.executor_address, self.chain, self.config)?))
|
||||
}
|
||||
"vm:maverick_v2" => Ok(Box::new(MaverickV2SwapEncoder::new(
|
||||
self.executor_address,
|
||||
self.chain,
|
||||
self.config,
|
||||
)?)),
|
||||
_ => Err(EncodingError::FatalError(format!(
|
||||
"Unknown protocol system: {}",
|
||||
self.protocol_system
|
||||
|
||||
@@ -590,6 +590,49 @@ impl SwapEncoder for CurveSwapEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a swap on a Maverick V2 pool through the given executor address.
|
||||
///
|
||||
/// # Fields
|
||||
/// * `executor_address` - The address of the executor contract that will perform the swap.
|
||||
#[derive(Clone)]
|
||||
pub struct MaverickV2SwapEncoder {
|
||||
executor_address: String,
|
||||
}
|
||||
|
||||
impl SwapEncoder for MaverickV2SwapEncoder {
|
||||
fn new(
|
||||
executor_address: String,
|
||||
_chain: Chain,
|
||||
_config: Option<HashMap<String, String>>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
Ok(Self { executor_address })
|
||||
}
|
||||
|
||||
fn encode_swap(
|
||||
&self,
|
||||
swap: Swap,
|
||||
encoding_context: EncodingContext,
|
||||
) -> Result<Vec<u8>, EncodingError> {
|
||||
let component_id = AlloyBytes::from_str(&swap.component.id)
|
||||
.map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?;
|
||||
|
||||
let args = (
|
||||
bytes_to_address(&swap.token_in)?,
|
||||
component_id,
|
||||
bytes_to_address(&encoding_context.receiver)?,
|
||||
(encoding_context.transfer_type as u8).to_be_bytes(),
|
||||
);
|
||||
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;
|
||||
@@ -602,7 +645,7 @@ mod tests {
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::encoding::models::TransferType;
|
||||
use crate::encoding::{evm::utils::write_calldata_to_file, models::TransferType};
|
||||
|
||||
mod uniswap_v2 {
|
||||
use super::*;
|
||||
@@ -1500,4 +1543,59 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_maverick_v2() {
|
||||
// GHO -> (maverick) -> USDC
|
||||
let maverick_pool = ProtocolComponent {
|
||||
id: String::from("0x14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67"),
|
||||
protocol_system: String::from("vm:maverick_v2"),
|
||||
..Default::default()
|
||||
};
|
||||
let token_in = Bytes::from("0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f");
|
||||
let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
|
||||
let swap = Swap {
|
||||
component: maverick_pool,
|
||||
token_in: token_in.clone(),
|
||||
token_out: token_out.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
let encoding_context = EncodingContext {
|
||||
// The receiver was generated with `makeAddr("bob") using forge`
|
||||
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),
|
||||
exact_out: false,
|
||||
router_address: Some(Bytes::default()),
|
||||
group_token_in: token_in.clone(),
|
||||
group_token_out: token_out.clone(),
|
||||
transfer_type: TransferType::TransferToProtocol,
|
||||
};
|
||||
let encoder = MaverickV2SwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
TychoCoreChain::Ethereum.into(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoded_swap = encoder
|
||||
.encode_swap(swap, encoding_context)
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
assert_eq!(
|
||||
hex_swap,
|
||||
String::from(concat!(
|
||||
// token in
|
||||
"40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f",
|
||||
// pool
|
||||
"14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67",
|
||||
// receiver
|
||||
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
|
||||
// transfer from router to protocol
|
||||
"00",
|
||||
))
|
||||
.to_lowercase()
|
||||
);
|
||||
|
||||
write_calldata_to_file("test_encode_maverick_v2", hex_swap.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub trait TychoEncoder {
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `solutions` - Vector of solutions to encode, each potentially using different setups (swap
|
||||
/// paths, protocols, etc.)
|
||||
/// paths, protocols, wrapping, etc.)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<Vec<Transaction>, EncodingError>` - Vector of executable transactions
|
||||
|
||||
Reference in New Issue
Block a user