Merge branch 'main' into router/hr/ENG-4237-refactor-usv3-callback

This commit is contained in:
Harsh Vardhan Roy
2025-02-17 21:56:49 +05:30
committed by GitHub
8 changed files with 342 additions and 131 deletions

View File

@@ -1,11 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;
import "../../src/executors/UniswapV4Executor.sol";
import "./UniswapV4Utils.sol";
import "@src/executors/UniswapV4Executor.sol";
import {Constants} from "../Constants.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {console} from "forge-std/console.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
contract UniswapV4ExecutorExposed is UniswapV4Executor {
constructor(IPoolManager _poolManager) UniswapV4Executor(_poolManager) {}
@@ -16,8 +17,11 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
returns (
address tokenIn,
address tokenOut,
bool isExactInput,
uint256 amount
uint256 amountOutMin,
bool zeroForOne,
address callbackExecutor,
bytes4 callbackSelector,
UniswapV4Pool[] memory pools
)
{
return _decodeData(data);
@@ -40,31 +44,84 @@ contract UniswapV4ExecutorTest is Test, Constants {
}
function testDecodeParams() public view {
uint24 expectedPoolFee = 500;
uint128 expectedAmount = 100;
uint256 minAmountOut = 100;
bool zeroForOne = true;
uint24 pool1Fee = 500;
int24 tickSpacing1 = 60;
uint24 pool2Fee = 1000;
int24 tickSpacing2 = -10;
bytes memory data = UniswapV4Utils.encodeExactInputSingle(
USDE_ADDR, USDT_ADDR, expectedPoolFee, false, 1, expectedAmount
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](2);
pools[0] = UniswapV4Executor.UniswapV4Pool({
intermediaryToken: USDT_ADDR,
fee: pool1Fee,
tickSpacing: tickSpacing1
});
pools[1] = UniswapV4Executor.UniswapV4Pool({
intermediaryToken: USDE_ADDR,
fee: pool2Fee,
tickSpacing: tickSpacing2
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
USDT_ADDR,
minAmountOut,
zeroForOne,
address(uniswapV4Exposed),
SafeCallback.unlockCallback.selector,
pools
);
(address tokenIn, address tokenOut, bool isExactInput, uint256 amount) =
uniswapV4Exposed.decodeData(data);
(
address tokenIn,
address tokenOut,
uint256 amountOutMin,
bool zeroForOneDecoded,
address callbackExecutor,
bytes4 callbackSelector,
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
) = uniswapV4Exposed.decodeData(data);
assertEq(tokenIn, USDE_ADDR);
assertEq(tokenOut, USDT_ADDR);
assertTrue(isExactInput);
assertEq(amount, expectedAmount);
assertEq(amountOutMin, minAmountOut);
assertEq(zeroForOneDecoded, zeroForOne);
assertEq(callbackExecutor, address(uniswapV4Exposed));
assertEq(callbackSelector, SafeCallback.unlockCallback.selector);
assertEq(decodedPools.length, 2);
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
assertEq(decodedPools[0].fee, pool1Fee);
assertEq(decodedPools[0].tickSpacing, tickSpacing1);
assertEq(decodedPools[1].intermediaryToken, USDE_ADDR);
assertEq(decodedPools[1].fee, pool2Fee);
assertEq(decodedPools[1].tickSpacing, tickSpacing2);
}
function testSwap() public {
function testSingleSwap() public {
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager);
uint256 usdeBalanceBeforeSwapExecutor =
USDE.balanceOf(address(uniswapV4Exposed));
bytes memory data = UniswapV4Utils.encodeExactInputSingle(
USDE_ADDR, USDT_ADDR, 100, true, 1, uint128(amountIn)
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](1);
pools[0] = UniswapV4Executor.UniswapV4Pool({
intermediaryToken: USDT_ADDR,
fee: uint24(100),
tickSpacing: int24(1)
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
USDT_ADDR,
uint256(1),
true,
address(uniswapV4Exposed),
SafeCallback.unlockCallback.selector,
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
@@ -75,4 +132,46 @@ contract UniswapV4ExecutorTest is Test, Constants {
);
assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut);
}
function testMultipleSwap() public {
// USDE -> USDT -> WBTC
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager);
uint256 usdeBalanceBeforeSwapExecutor =
USDE.balanceOf(address(uniswapV4Exposed));
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](2);
pools[0] = UniswapV4Executor.UniswapV4Pool({
intermediaryToken: USDT_ADDR,
fee: uint24(100),
tickSpacing: int24(1)
});
pools[1] = UniswapV4Executor.UniswapV4Pool({
intermediaryToken: WBTC_ADDR,
fee: uint24(3000),
tickSpacing: int24(60)
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR,
WBTC_ADDR,
uint256(1),
true,
address(uniswapV4Exposed),
SafeCallback.unlockCallback.selector,
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
assertEq(
USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn
);
assertTrue(
IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut
);
}
}

View File

@@ -4,43 +4,34 @@ pragma solidity ^0.8.26;
import "@src/executors/UniswapV4Executor.sol";
library UniswapV4Utils {
function encodeExactInputSingle(
function encodeExactInput(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountOutMin,
bool zeroForOne,
uint24 tickSpacing,
uint128 amountIn
address callbackExecutor,
bytes4 callbackSelector,
UniswapV4Executor.UniswapV4Pool[] memory pools
) public pure returns (bytes memory) {
PoolKey memory key = PoolKey({
currency0: Currency.wrap(zeroForOne ? tokenIn : tokenOut),
currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn),
fee: fee,
tickSpacing: int24(tickSpacing),
hooks: IHooks(address(0))
});
bytes memory encodedPools;
bytes memory actions = abi.encodePacked(
uint8(Actions.SWAP_EXACT_IN_SINGLE),
uint8(Actions.SETTLE_ALL),
uint8(Actions.TAKE_ALL)
for (uint256 i = 0; i < pools.length; i++) {
encodedPools = abi.encodePacked(
encodedPools,
pools[i].intermediaryToken,
bytes3(pools[i].fee),
pools[i].tickSpacing
);
}
return abi.encodePacked(
tokenIn,
tokenOut,
amountOutMin,
zeroForOne,
callbackExecutor,
bytes4(callbackSelector),
encodedPools
);
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
IV4Router.ExactInputSingleParams({
poolKey: key,
zeroForOne: zeroForOne,
amountIn: amountIn,
amountOutMinimum: 0,
hookData: bytes("")
})
);
params[1] = abi.encode(key.currency0, amountIn);
params[2] = abi.encode(key.currency1, 0);
return abi.encode(actions, params);
}
}