Merge branch 'main' into feat/maverick-v2-executor

This commit is contained in:
Tamara
2025-04-29 12:01:20 -04:00
committed by GitHub
57 changed files with 9339 additions and 5667 deletions

View File

@@ -1,11 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "../TestUtils.sol";
import "@src/executors/BalancerV2Executor.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {Constants} from "../Constants.sol";
contract BalancerV2ExecutorExposed is BalancerV2Executor {
constructor(address _permit2) BalancerV2Executor(_permit2) {}
function decodeParams(bytes calldata data)
external
pure
@@ -14,18 +16,15 @@ contract BalancerV2ExecutorExposed is BalancerV2Executor {
IERC20 tokenOut,
bytes32 poolId,
address receiver,
bool needsApproval
bool needsApproval,
TransferType transferType
)
{
return _decodeData(data);
}
}
contract BalancerV2ExecutorTest is
BalancerV2ExecutorExposed,
Test,
Constants
{
contract BalancerV2ExecutorTest is Constants, TestUtils {
using SafeERC20 for IERC20;
BalancerV2ExecutorExposed balancerV2Exposed;
@@ -37,12 +36,17 @@ contract BalancerV2ExecutorTest is
function setUp() public {
uint256 forkBlock = 17323404;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
balancerV2Exposed = new BalancerV2ExecutorExposed();
balancerV2Exposed = new BalancerV2ExecutorExposed(PERMIT2_ADDRESS);
}
function testDecodeParams() public view {
bytes memory params = abi.encodePacked(
WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true
WETH_ADDR,
BAL_ADDR,
WETH_BAL_POOL_ID,
address(2),
true,
TokenTransfer.TransferType.NONE
);
(
@@ -50,7 +54,8 @@ contract BalancerV2ExecutorTest is
IERC20 tokenOut,
bytes32 poolId,
address receiver,
bool needsApproval
bool needsApproval,
TokenTransfer.TransferType transferType
) = balancerV2Exposed.decodeParams(params);
assertEq(address(tokenIn), WETH_ADDR);
@@ -58,6 +63,7 @@ contract BalancerV2ExecutorTest is
assertEq(poolId, WETH_BAL_POOL_ID);
assertEq(receiver, address(2));
assertEq(needsApproval, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
}
function testDecodeParamsInvalidDataLength() public {
@@ -70,8 +76,14 @@ contract BalancerV2ExecutorTest is
function testSwap() public {
uint256 amountIn = 10 ** 18;
bytes memory protocolData =
abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true);
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
BAL_ADDR,
WETH_BAL_POOL_ID,
BOB,
true,
TokenTransfer.TransferType.NONE
);
deal(WETH_ADDR, address(balancerV2Exposed), amountIn);
uint256 balanceBefore = BAL.balanceOf(BOB);
@@ -84,16 +96,15 @@ contract BalancerV2ExecutorTest is
}
function testDecodeIntegration() public view {
// Generated by the SwapEncoder - test_encode_balancer_v2
bytes memory protocolData =
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01";
loadCallDataFromFile("test_encode_balancer_v2");
(
IERC20 tokenIn,
IERC20 tokenOut,
bytes32 poolId,
address receiver,
bool needsApproval
bool needsApproval,
TokenTransfer.TransferType transferType
) = balancerV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), WETH_ADDR);
@@ -101,12 +112,13 @@ contract BalancerV2ExecutorTest is
assertEq(poolId, WETH_BAL_POOL_ID);
assertEq(receiver, BOB);
assertEq(needsApproval, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
}
function testSwapIntegration() public {
// Generated by the SwapEncoder - test_encode_balancer_v2
bytes memory protocolData =
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01";
loadCallDataFromFile("test_encode_balancer_v2");
uint256 amountIn = 10 ** 18;
deal(WETH_ADDR, address(balancerV2Exposed), amountIn);

View File

@@ -22,7 +22,9 @@ interface MetaRegistry {
}
contract CurveExecutorExposed is CurveExecutor {
constructor(address _nativeToken) CurveExecutor(_nativeToken) {}
constructor(address _nativeToken, address _permit2)
CurveExecutor(_nativeToken, _permit2)
{}
function decodeData(bytes calldata data)
external
@@ -34,7 +36,9 @@ contract CurveExecutorExposed is CurveExecutor {
uint8 poolType,
int128 i,
int128 j,
bool tokenApprovalNeeded
bool tokenApprovalNeeded,
TokenTransfer.TransferType transferType,
address receiver
)
{
return _decodeData(data);
@@ -50,7 +54,8 @@ contract CurveExecutorTest is Test, Constants {
function setUp() public {
uint256 forkBlock = 22031795;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
curveExecutorExposed = new CurveExecutorExposed(ETH_ADDR_FOR_CURVE);
curveExecutorExposed =
new CurveExecutorExposed(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS);
metaRegistry = MetaRegistry(CURVE_META_REGISTRY);
}
@@ -62,7 +67,9 @@ contract CurveExecutorTest is Test, Constants {
uint8(3),
uint8(2),
uint8(0),
true
true,
TokenTransfer.TransferType.NONE,
ALICE
);
(
@@ -72,7 +79,9 @@ contract CurveExecutorTest is Test, Constants {
uint8 poolType,
int128 i,
int128 j,
bool tokenApprovalNeeded
bool tokenApprovalNeeded,
TokenTransfer.TransferType transferType,
address receiver
) = curveExecutorExposed.decodeData(data);
assertEq(tokenIn, WETH_ADDR);
@@ -82,6 +91,8 @@ contract CurveExecutorTest is Test, Constants {
assertEq(i, 2);
assertEq(j, 0);
assertEq(tokenApprovalNeeded, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
assertEq(receiver, ALICE);
}
function testTriPool() public {
@@ -89,15 +100,12 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 1 ether;
deal(DAI_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(DAI_ADDR, USDC_ADDR, TRIPOOL, 1);
bytes memory data = _getData(DAI_ADDR, USDC_ADDR, TRIPOOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 999797);
assertEq(
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), amountOut);
}
function testStEthPool() public {
@@ -106,14 +114,14 @@ contract CurveExecutorTest is Test, Constants {
deal(address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(ETH_ADDR_FOR_CURVE, STETH_ADDR, STETH_POOL, 1);
_getData(ETH_ADDR_FOR_CURVE, STETH_ADDR, STETH_POOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 1001072414418410897);
assertEq(
IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
IERC20(STETH_ADDR).balanceOf(ALICE),
amountOut - 1 // there is something weird in this pool, but won't investigate for now because we don't currently support it in the simulation
);
}
@@ -122,15 +130,13 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 1 ether;
deal(WETH_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL, 3);
bytes memory data =
_getData(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL, 3, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 2279618);
assertEq(
IERC20(WBTC_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), amountOut);
}
function testSUSDPool() public {
@@ -138,15 +144,12 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 100 * 10 ** 6;
deal(USDC_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(USDC_ADDR, SUSD_ADDR, SUSD_POOL, 1);
bytes memory data = _getData(USDC_ADDR, SUSD_ADDR, SUSD_POOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 100488101605550214590);
assertEq(
IERC20(SUSD_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(SUSD_ADDR).balanceOf(ALICE), amountOut);
}
function testFraxUsdcPool() public {
@@ -154,15 +157,13 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 1 ether;
deal(FRAX_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL, 1);
bytes memory data =
_getData(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 998097);
assertEq(
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), amountOut);
}
function testUsdeUsdcPool() public {
@@ -170,15 +171,13 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 100 * 10 ** 6;
deal(USDC_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL, 1);
bytes memory data =
_getData(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 100064812138999986170);
assertEq(
IERC20(USDE_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(USDE_ADDR).balanceOf(ALICE), amountOut);
}
function testDolaFraxPyusdPool() public {
@@ -187,32 +186,27 @@ contract CurveExecutorTest is Test, Constants {
deal(DOLA_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL, 1);
_getData(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 99688992);
assertEq(
IERC20(FRAXPYUSD_POOL).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(FRAXPYUSD_POOL).balanceOf(ALICE), amountOut);
}
function testCryptoPoolWithETH() public {
// Swapping XYO -> ETH on a CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99
uint256 amountIn = 1 ether;
uint256 initialBalance = address(curveExecutorExposed).balance; // this address already has some ETH assigned to it
uint256 initialBalance = address(ALICE).balance; // this address already has some ETH assigned to it
deal(XYO_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(XYO_ADDR, ETH_ADDR_FOR_CURVE, ETH_XYO_POOL, 2);
_getData(XYO_ADDR, ETH_ADDR_FOR_CURVE, ETH_XYO_POOL, 2, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 6081816039338);
assertEq(
address(curveExecutorExposed).balance, initialBalance + amountOut
);
assertEq(ALICE.balance, initialBalance + amountOut);
}
function testCryptoPool() public {
@@ -220,15 +214,13 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 1000 ether;
deal(BSGG_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL, 2);
bytes memory data =
_getData(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL, 2, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 23429);
assertEq(
IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(USDT_ADDR).balanceOf(ALICE), amountOut);
}
function testTricryptoPool() public {
@@ -236,15 +228,13 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 1 ether;
deal(WETH_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL, 2);
bytes memory data =
_getData(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL, 2, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 1861130974);
assertEq(
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), amountOut);
}
function testTwoCryptoPool() public {
@@ -252,32 +242,27 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 1 ether;
deal(UWU_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL, 2);
bytes memory data =
_getData(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL, 2, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 2873786684675);
assertEq(
IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), amountOut);
}
function testStableSwapPool() public {
// Swapping CRVUSD -> USDT on a StableSwap pool, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d (plain pool)
uint256 amountIn = 1 ether;
deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn);
deal(USDT_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(CRVUSD_ADDR, USDT_ADDR, CRVUSD_USDT_POOL, 1);
_getData(USDT_ADDR, CRVUSD_ADDR, CRVUSD_USDT_POOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 999910);
assertEq(
IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(amountOut, 10436946786333182306400100);
assertEq(IERC20(CRVUSD_ADDR).balanceOf(ALICE), amountOut);
}
function testMetaPool() public {
@@ -286,22 +271,20 @@ contract CurveExecutorTest is Test, Constants {
deal(WTAO_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL, 1);
_getData(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL, 1, ALICE);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 32797923610);
assertEq(
IERC20(WSTTAO_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
assertEq(IERC20(WSTTAO_ADDR).balanceOf(ALICE), amountOut);
}
function _getData(
address tokenIn,
address tokenOut,
address pool,
uint8 poolType
uint8 poolType,
address receiver
) internal view returns (bytes memory data) {
(int128 i, int128 j) = _getIndexes(tokenIn, tokenOut, pool);
data = abi.encodePacked(
@@ -311,7 +294,9 @@ contract CurveExecutorTest is Test, Constants {
poolType,
uint8(uint256(uint128(i))),
uint8(uint256(uint128(j))),
true
true,
TokenTransfer.TransferType.NONE,
receiver
);
}

View File

@@ -1,14 +1,15 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import {EkuboExecutor} from "@src/executors/EkuboExecutor.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../TestUtils.sol";
import {Constants} from "../Constants.sol";
import {Test, console} from "forge-std/Test.sol";
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
import {EkuboExecutor, TokenTransfer} from "@src/executors/EkuboExecutor.sol";
import {ICore} from "@ekubo/interfaces/ICore.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
import {console} from "forge-std/Test.sol";
contract EkuboExecutorTest is Test, Constants {
contract EkuboExecutorTest is Constants, TestUtils {
address constant EXECUTOR_ADDRESS =
0xcA4F73Fe97D0B987a0D12B39BBD562c779BAb6f6; // Same address as in swap_encoder.rs tests
EkuboExecutor executor;
@@ -26,7 +27,7 @@ contract EkuboExecutorTest is Test, Constants {
deployCodeTo(
"executors/EkuboExecutor.sol",
abi.encode(CORE_ADDRESS),
abi.encode(CORE_ADDRESS, PERMIT2_ADDRESS),
EXECUTOR_ADDRESS
);
executor = EkuboExecutor(payable(EXECUTOR_ADDRESS));
@@ -44,6 +45,7 @@ contract EkuboExecutorTest is Test, Constants {
uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor));
bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
address(executor), // receiver
NATIVE_TOKEN_ADDRESS, // tokenIn
USDC_ADDR, // tokenOut
@@ -80,6 +82,7 @@ contract EkuboExecutorTest is Test, Constants {
uint256 ethBalanceBeforeExecutor = address(executor).balance;
bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
address(executor), // receiver
USDC_ADDR, // tokenIn
NATIVE_TOKEN_ADDRESS, // tokenOut
@@ -137,6 +140,7 @@ contract EkuboExecutorTest is Test, Constants {
// Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi
function testMultiHopSwap() public {
bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType
address(executor), // receiver
NATIVE_TOKEN_ADDRESS, // tokenIn
USDC_ADDR, // tokenOut of 1st swap
@@ -151,8 +155,6 @@ contract EkuboExecutorTest is Test, Constants {
// Data is generated by test case in swap_encoder::tests::ekubo::test_encode_swap_multi
function testMultiHopSwapIntegration() public {
multiHopSwap(
hex"ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032"
);
multiHopSwap(loadCallDataFromFile("test_ekubo_encode_swap_multi"));
}
}

View File

@@ -2,13 +2,18 @@
pragma solidity ^0.8.26;
import "@src/executors/UniswapV2Executor.sol";
import "@src/executors/TokenTransfer.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
contract UniswapV2ExecutorExposed is UniswapV2Executor {
constructor(address _factory, bytes32 _initCode)
UniswapV2Executor(_factory, _initCode)
{}
constructor(
address _factory,
bytes32 _initCode,
address _permit2,
uint256 _feeBps
) UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) {}
function decodeParams(bytes calldata data)
external
@@ -17,7 +22,8 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor {
IERC20 inToken,
address target,
address receiver,
bool zeroForOne
bool zeroForOne,
TransferType transferType
)
{
return _decodeData(data);
@@ -46,7 +52,7 @@ contract FakeUniswapV2Pool {
}
}
contract UniswapV2ExecutorTest is Test, Constants {
contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
using SafeERC20 for IERC20;
UniswapV2ExecutorExposed uniswapV2Exposed;
@@ -54,32 +60,54 @@ contract UniswapV2ExecutorTest is Test, Constants {
UniswapV2ExecutorExposed pancakeswapV2Exposed;
IERC20 WETH = IERC20(WETH_ADDR);
IERC20 DAI = IERC20(DAI_ADDR);
IAllowanceTransfer permit2;
function setUp() public {
uint256 forkBlock = 17323404;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
uniswapV2Exposed = new UniswapV2ExecutorExposed(
USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH
USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, 30
);
sushiswapV2Exposed = new UniswapV2ExecutorExposed(
SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH
SUSHISWAPV2_FACTORY_ETHEREUM,
SUSHIV2_POOL_CODE_INIT_HASH,
PERMIT2_ADDRESS,
30
);
pancakeswapV2Exposed = new UniswapV2ExecutorExposed(
PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH
PANCAKESWAPV2_FACTORY_ETHEREUM,
PANCAKEV2_POOL_CODE_INIT_HASH,
PERMIT2_ADDRESS,
25
);
permit2 = IAllowanceTransfer(PERMIT2_ADDRESS);
}
function testDecodeParams() public view {
bytes memory params =
abi.encodePacked(WETH_ADDR, address(2), address(3), false);
bytes memory params = abi.encodePacked(
WETH_ADDR,
address(2),
address(3),
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
(IERC20 tokenIn, address target, address receiver, bool zeroForOne) =
uniswapV2Exposed.decodeParams(params);
(
IERC20 tokenIn,
address target,
address receiver,
bool zeroForOne,
TokenTransfer.TransferType transferType
) = uniswapV2Exposed.decodeParams(params);
assertEq(address(tokenIn), WETH_ADDR);
assertEq(target, address(2));
assertEq(receiver, address(3));
assertEq(zeroForOne, false);
assertEq(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL),
uint8(transferType)
);
}
function testDecodeParamsInvalidDataLength() public {
@@ -126,12 +154,17 @@ contract UniswapV2ExecutorTest is Test, Constants {
assertGe(amountOut, 0);
}
function testSwap() public {
function testSwapWithTransfer() public {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData =
abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne);
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
uniswapV2Exposed.swap(amountIn, protocolData);
@@ -140,24 +173,102 @@ contract UniswapV2ExecutorTest is Test, Constants {
assertGe(finalBalance, amountOut);
}
function testDecodeIntegration() public view {
// Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode
bytes memory protocolData =
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100";
function testSwapWithTransferFrom() public {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL)
);
(IERC20 tokenIn, address target, address receiver, bool zeroForOne) =
uniswapV2Exposed.decodeParams(protocolData);
deal(WETH_ADDR, address(this), amountIn);
IERC20(WETH_ADDR).approve(address(uniswapV2Exposed), amountIn);
uniswapV2Exposed.swap(amountIn, protocolData);
uint256 finalBalance = DAI.balanceOf(BOB);
assertGe(finalBalance, amountOut);
}
function testSwapWithPermit2TransferFrom() public {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
ALICE,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL)
);
deal(WETH_ADDR, ALICE, amountIn);
vm.startPrank(ALICE);
(
IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature
) = handlePermit2Approval(
WETH_ADDR, address(uniswapV2Exposed), amountIn
);
// Assume the permit2.approve method will be called from the TychoRouter
// Replicate this scenario in this test.
permit2.permit(ALICE, permitSingle, signature);
uniswapV2Exposed.swap(amountIn, protocolData);
vm.stopPrank();
uint256 finalBalance = DAI.balanceOf(ALICE);
assertGe(finalBalance, amountOut);
}
function testSwapNoTransfer() public {
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.NONE)
);
deal(WETH_ADDR, address(this), amountIn);
IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn);
uniswapV2Exposed.swap(amountIn, protocolData);
uint256 finalBalance = DAI.balanceOf(BOB);
assertGe(finalBalance, amountOut);
}
function testDecodeIntegration() public view {
bytes memory protocolData =
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000";
(
IERC20 tokenIn,
address target,
address receiver,
bool zeroForOne,
TokenTransfer.TransferType transferType
) = uniswapV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), WETH_ADDR);
assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);
assertEq(receiver, 0x0000000000000000000000000000000000000001);
assertEq(zeroForOne, false);
// TRANSFER = 0
assertEq(0, uint8(transferType));
}
function testSwapIntegration() public {
// Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode
bytes memory protocolData =
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00";
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000";
uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891;
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
@@ -171,8 +282,13 @@ contract UniswapV2ExecutorTest is Test, Constants {
uint256 amountIn = 10 ** 18;
bool zeroForOne = false;
address fakePool = address(new FakeUniswapV2Pool(WETH_ADDR, DAI_ADDR));
bytes memory protocolData =
abi.encodePacked(WETH_ADDR, fakePool, BOB, zeroForOne);
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
fakePool,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
vm.expectRevert(UniswapV2Executor__InvalidTarget.selector);
@@ -186,8 +302,13 @@ contract UniswapV2ExecutorTest is Test, Constants {
vm.rollFork(26857267);
uint256 amountIn = 10 * 10 ** 6;
bool zeroForOne = true;
bytes memory protocolData =
abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne);
bytes memory protocolData = abi.encodePacked(
BASE_USDC,
USDC_MAG7_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
deal(BASE_USDC, address(uniswapV2Exposed), amountIn);

View File

@@ -2,12 +2,14 @@
pragma solidity ^0.8.26;
import "@src/executors/UniswapV3Executor.sol";
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
contract UniswapV3ExecutorExposed is UniswapV3Executor {
constructor(address _factory, bytes32 _initCode)
UniswapV3Executor(_factory, _initCode)
constructor(address _factory, bytes32 _initCode, address _permit2)
UniswapV3Executor(_factory, _initCode, _permit2)
{}
function decodeData(bytes calldata data)
@@ -19,7 +21,8 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor {
uint24 fee,
address receiver,
address target,
bool zeroForOne
bool zeroForOne,
TransferType transferType
)
{
return _decodeData(data);
@@ -35,30 +38,40 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor {
}
}
contract UniswapV3ExecutorTest is Test, Constants {
contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
using SafeERC20 for IERC20;
UniswapV3ExecutorExposed uniswapV3Exposed;
UniswapV3ExecutorExposed pancakeV3Exposed;
IERC20 WETH = IERC20(WETH_ADDR);
IERC20 DAI = IERC20(DAI_ADDR);
IAllowanceTransfer permit2;
function setUp() public {
uint256 forkBlock = 17323404;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
uniswapV3Exposed = new UniswapV3ExecutorExposed(
USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH
USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS
);
pancakeV3Exposed = new UniswapV3ExecutorExposed(
PANCAKESWAPV3_DEPLOYER_ETHEREUM, PANCAKEV3_POOL_CODE_INIT_HASH
PANCAKESWAPV3_DEPLOYER_ETHEREUM,
PANCAKEV3_POOL_CODE_INIT_HASH,
PERMIT2_ADDRESS
);
permit2 = IAllowanceTransfer(PERMIT2_ADDRESS);
}
function testDecodeParams() public view {
uint24 expectedPoolFee = 500;
bytes memory data = abi.encodePacked(
WETH_ADDR, DAI_ADDR, expectedPoolFee, address(2), address(3), false
WETH_ADDR,
DAI_ADDR,
expectedPoolFee,
address(2),
address(3),
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
(
@@ -67,7 +80,8 @@ contract UniswapV3ExecutorTest is Test, Constants {
uint24 fee,
address receiver,
address target,
bool zeroForOne
bool zeroForOne,
TokenTransfer.TransferType transferType
) = uniswapV3Exposed.decodeData(data);
assertEq(tokenIn, WETH_ADDR);
@@ -76,6 +90,33 @@ contract UniswapV3ExecutorTest is Test, Constants {
assertEq(receiver, address(2));
assertEq(target, address(3));
assertEq(zeroForOne, false);
assertEq(
uint8(transferType),
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
}
function testSwapIntegration() public {
uint256 amountIn = 10 ** 18;
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
bool zeroForOne = false;
bytes memory data = encodeUniswapV3Swap(
WETH_ADDR,
DAI_ADDR,
address(this),
DAI_WETH_USV3,
zeroForOne,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
uint256 amountOut = uniswapV3Exposed.swap(amountIn, data);
assertGe(amountOut, expAmountOut);
assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0);
assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut);
}
function testDecodeParamsInvalidDataLength() public {
@@ -105,12 +146,18 @@ contract UniswapV3ExecutorTest is Test, Constants {
uint256 initialPoolReserve = IERC20(WETH_ADDR).balanceOf(DAI_WETH_USV3);
vm.startPrank(DAI_WETH_USV3);
bytes memory protocolData =
abi.encodePacked(WETH_ADDR, DAI_ADDR, poolFee);
bytes memory protocolData = abi.encodePacked(
WETH_ADDR,
DAI_ADDR,
poolFee,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
address(uniswapV3Exposed)
);
uint256 dataOffset = 3; // some offset
uint256 dataLength = protocolData.length;
bytes memory callbackData = abi.encodePacked(
bytes4(0xfa461e33),
int256(amountOwed), // amount0Delta
int256(0), // amount1Delta
dataOffset,
@@ -124,24 +171,6 @@ contract UniswapV3ExecutorTest is Test, Constants {
assertEq(finalPoolReserve - initialPoolReserve, amountOwed);
}
function testSwapIntegration() public {
uint256 amountIn = 10 ** 18;
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
bool zeroForOne = false;
bytes memory data = encodeUniswapV3Swap(
WETH_ADDR, DAI_ADDR, address(this), DAI_WETH_USV3, zeroForOne
);
uint256 amountOut = uniswapV3Exposed.swap(amountIn, data);
assertGe(amountOut, expAmountOut);
assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0);
assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut);
}
function testSwapFailureInvalidTarget() public {
uint256 amountIn = 10 ** 18;
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);
@@ -154,7 +183,8 @@ contract UniswapV3ExecutorTest is Test, Constants {
uint24(3000),
address(this),
fakePool,
zeroForOne
zeroForOne,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
vm.expectRevert(UniswapV3Executor__InvalidTarget.selector);
@@ -166,11 +196,18 @@ contract UniswapV3ExecutorTest is Test, Constants {
address tokenOut,
address receiver,
address target,
bool zero2one
bool zero2one,
TokenTransfer.TransferType transferType
) internal view returns (bytes memory) {
IUniswapV3Pool pool = IUniswapV3Pool(target);
return abi.encodePacked(
tokenIn, tokenOut, pool.fee(), receiver, target, zero2one
tokenIn,
tokenOut,
pool.fee(),
receiver,
target,
zero2one,
transferType
);
}
}

View File

@@ -2,14 +2,18 @@
pragma solidity ^0.8.26;
import "../../src/executors/UniswapV4Executor.sol";
import "../TestUtils.sol";
import "./UniswapV4Utils.sol";
import "@src/executors/TokenTransfer.sol";
import "@src/executors/UniswapV4Executor.sol";
import {Constants} from "../Constants.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
contract UniswapV4ExecutorExposed is UniswapV4Executor {
constructor(IPoolManager _poolManager) UniswapV4Executor(_poolManager) {}
constructor(IPoolManager _poolManager, address _permit2)
UniswapV4Executor(_poolManager, _permit2)
{}
function decodeData(bytes calldata data)
external
@@ -18,7 +22,8 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
address tokenIn,
address tokenOut,
bool zeroForOne,
address callbackExecutor,
TokenTransfer.TransferType transferType,
address receiver,
UniswapV4Pool[] memory pools
)
{
@@ -26,7 +31,7 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
}
}
contract UniswapV4ExecutorTest is Test, Constants {
contract UniswapV4ExecutorTest is Constants, TestUtils {
using SafeERC20 for IERC20;
UniswapV4ExecutorExposed uniswapV4Exposed;
@@ -37,8 +42,9 @@ contract UniswapV4ExecutorTest is Test, Constants {
function setUp() public {
uint256 forkBlock = 21817316;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
uniswapV4Exposed =
new UniswapV4ExecutorExposed(IPoolManager(poolManager));
uniswapV4Exposed = new UniswapV4ExecutorExposed(
IPoolManager(poolManager), PERMIT2_ADDRESS
);
}
function testDecodeParams() public view {
@@ -47,6 +53,8 @@ contract UniswapV4ExecutorTest is Test, Constants {
int24 tickSpacing1 = 60;
uint24 pool2Fee = 1000;
int24 tickSpacing2 = -10;
TokenTransfer.TransferType transferType =
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL;
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](2);
@@ -62,21 +70,23 @@ contract UniswapV4ExecutorTest is Test, Constants {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, zeroForOne, address(uniswapV4Exposed), pools
USDE_ADDR, USDT_ADDR, zeroForOne, transferType, ALICE, pools
);
(
address tokenIn,
address tokenOut,
bool zeroForOneDecoded,
address callbackExecutor,
TokenTransfer.TransferType transferTypeDecoded,
address receiver,
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
) = uniswapV4Exposed.decodeData(data);
assertEq(tokenIn, USDE_ADDR);
assertEq(tokenOut, USDT_ADDR);
assertEq(zeroForOneDecoded, zeroForOne);
assertEq(callbackExecutor, address(uniswapV4Exposed));
assertEq(uint8(transferTypeDecoded), uint8(transferType));
assertEq(receiver, ALICE);
assertEq(decodedPools.length, 2);
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
assertEq(decodedPools[0].fee, pool1Fee);
@@ -102,7 +112,12 @@ contract UniswapV4ExecutorTest is Test, Constants {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, true, address(uniswapV4Exposed), pools
USDE_ADDR,
USDT_ADDR,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
ALICE,
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
@@ -111,14 +126,13 @@ contract UniswapV4ExecutorTest is Test, Constants {
USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn
);
assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut);
assertTrue(USDT.balanceOf(ALICE) == amountOut);
}
function testSingleSwapIntegration() public {
// USDE -> USDT
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap
bytes memory protocolData =
hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec7000064000001";
loadCallDataFromFile("test_encode_uniswap_v4_simple_swap");
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager);
@@ -128,10 +142,9 @@ contract UniswapV4ExecutorTest is Test, Constants {
uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
assertEq(
USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn
USDE.balanceOf(ALICE), usdeBalanceBeforeSwapExecutor - amountIn
);
assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut);
assertTrue(USDT.balanceOf(ALICE) == amountOut);
}
function testMultipleSwap() public {
@@ -156,7 +169,12 @@ contract UniswapV4ExecutorTest is Test, Constants {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, WBTC_ADDR, true, address(uniswapV4Exposed), pools
USDE_ADDR,
WBTC_ADDR,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
ALICE,
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
@@ -165,17 +183,13 @@ contract UniswapV4ExecutorTest is Test, Constants {
USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn
);
assertTrue(
IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut
);
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
}
function testMultipleSwapIntegration() public {
// USDE -> USDT -> WBTC
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_sequential_swap
bytes memory protocolData =
hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c";
loadCallDataFromFile("test_encode_uniswap_v4_sequential_swap");
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
@@ -189,8 +203,6 @@ contract UniswapV4ExecutorTest is Test, Constants {
USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn
);
assertTrue(
IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut
);
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
}
}

View File

@@ -8,7 +8,8 @@ library UniswapV4Utils {
address tokenIn,
address tokenOut,
bool zeroForOne,
address callbackExecutor,
UniswapV4Executor.TransferType transferType,
address receiver,
UniswapV4Executor.UniswapV4Pool[] memory pools
) public pure returns (bytes memory) {
bytes memory encodedPools;
@@ -23,7 +24,7 @@ library UniswapV4Utils {
}
return abi.encodePacked(
tokenIn, tokenOut, zeroForOne, callbackExecutor, encodedPools
tokenIn, tokenOut, zeroForOne, transferType, receiver, encodedPools
);
}
}