diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index 00ca24b..bee3dcb 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -2,14 +2,20 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; -import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { + IERC20, + SafeERC20 +} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import { + Currency, CurrencyLibrary +} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {V4Router} from "@uniswap/v4-periphery/src/V4Router.sol"; import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol"; import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol"; +import "forge-std/console.sol"; error UniswapV4Executor__InvalidDataLength(); error UniswapV4Executor__SwapFailed(); @@ -20,21 +26,23 @@ contract UniswapV4Executor is IExecutor, V4Router { constructor(IPoolManager _poolManager) V4Router(_poolManager) {} - function swap( - uint256 amountIn, - bytes calldata data - ) external payable returns (uint256 amountOut) { + function swap(uint256 amountIn, bytes calldata data) + external + payable + returns (uint256 amountOut) + { ( address tokenIn, address tokenOut, uint24 fee, - address receiver, // TODO: This is not used right now + address receiver, bool zeroForOne, uint24 tickSpacing ) = _decodeData(data); uint128 amountIn128 = uint128(amountIn); - uint128 amountOut128 = uint128(amountOut); + uint256 balanceBefore = IERC20(tokenOut).balanceOf(receiver); + PoolKey memory key = PoolKey({ currency0: Currency.wrap(zeroForOne ? tokenIn : tokenOut), currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn), @@ -46,7 +54,7 @@ contract UniswapV4Executor is IExecutor, V4Router { bytes memory actions = abi.encodePacked( uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), - uint8(Actions.TAKE_ALL) + uint8(Actions.TAKE) ); bytes[] memory params = new bytes[](3); @@ -56,17 +64,20 @@ contract UniswapV4Executor is IExecutor, V4Router { poolKey: key, zeroForOne: zeroForOne, amountIn: amountIn128, - amountOutMinimum: amountOut128, + amountOutMinimum: 0, hookData: bytes("") }) ); params[1] = abi.encode(key.currency0, amountIn128); - params[2] = abi.encode(key.currency1, amountOut128); + params[2] = abi.encode(key.currency1, receiver, 0); this.executeActions(abi.encode(actions, params)); - // TODO: This is still hardcode to zero, find a way to return the actual amount out + amountOut = IERC20(tokenOut).balanceOf(receiver) - balanceBefore; + + if (amountOut == 0) revert UniswapV4Executor__SwapFailed(); + return amountOut; } @@ -74,9 +85,7 @@ contract UniswapV4Executor is IExecutor, V4Router { _executeActions(actions); } - function _decodeData( - bytes calldata data - ) + function _decodeData(bytes calldata data) internal pure returns ( @@ -100,11 +109,10 @@ contract UniswapV4Executor is IExecutor, V4Router { tickSpacing = uint24(bytes3(data[64:67])); } - function _pay( - Currency token, - address payer, - uint256 amount - ) internal override { + function _pay(Currency token, address payer, uint256 amount) + internal + override + { token.transfer(payer, amount); } diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 7439367..c0eca39 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -4,13 +4,12 @@ pragma solidity ^0.8.26; import "@src/executors/UniswapV4Executor.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; +import {console} from "forge-std/console.sol"; contract UniswapV4ExecutorExposed is UniswapV4Executor { constructor(IPoolManager _poolManager) UniswapV4Executor(_poolManager) {} - function decodeData( - bytes calldata data - ) + function decodeData(bytes calldata data) external pure returns ( @@ -32,24 +31,19 @@ contract UniswapV4ExecutorTest is Test, Constants { UniswapV4ExecutorExposed uniswapV4Exposed; IERC20 USDE = IERC20(USDE_ADDR); IERC20 USDT = IERC20(USDT_ADDR); + address poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90; function setUp() public { uint256 forkBlock = 21817316; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - uniswapV4Exposed = new UniswapV4ExecutorExposed( - IPoolManager(0x000000000004444c5dc75cB358380D2e3dE08A90) - ); + uniswapV4Exposed = + new UniswapV4ExecutorExposed(IPoolManager(poolManager)); } function testDecodeParamsUniswapV4() public view { uint24 expectedPoolFee = 500; bytes memory data = abi.encodePacked( - USDE_ADDR, - USDT_ADDR, - expectedPoolFee, - address(2), - false, - int24(1) + USDE_ADDR, USDT_ADDR, expectedPoolFee, address(2), false, int24(1) ); ( @@ -78,43 +72,30 @@ contract UniswapV4ExecutorTest is Test, Constants { function testSwapUniswapV4() public { vm.startPrank(BOB); - uint256 amountIn = 1 ether; + uint256 amountIn = 100 ether; deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); - assertEq(USDE.balanceOf(address(uniswapV4Exposed)), amountIn); + uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); + uint256 usdeBalanceBeforeSwapExecutor = + USDE.balanceOf(address(uniswapV4Exposed)); + assertEq(usdeBalanceBeforeSwapExecutor, amountIn); + uint256 usdtBalanceBeforeSwapBob = USDT.balanceOf(address(BOB)); + assertEq(usdtBalanceBeforeSwapBob, 0); bytes memory data = abi.encodePacked( USDE_ADDR, USDT_ADDR, uint24(100), // 0.01% fee tier - address(this), + BOB, true, int24(1) ); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); - assertEq(USDE.balanceOf(address(uniswapV4Exposed)), 0); - vm.stopPrank(); - } - - function testSwapUniswapV4With1Inch() public { - vm.startPrank(BOB); - uint256 amountIn = 1 ether; - deal(INCH_ADDR, address(uniswapV4Exposed), amountIn); + assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); assertEq( - IERC20(INCH_ADDR).balanceOf(address(uniswapV4Exposed)), - amountIn + USDE.balanceOf(address(uniswapV4Exposed)), + usdeBalanceBeforeSwapExecutor - amountIn ); - - bytes memory data = abi.encodePacked( - INCH_ADDR, - USDC_ADDR, - uint24(10000), // 0.01% fee tier - address(this), - true, - int24(200) - ); - - uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); - assertEq(IERC20(INCH_ADDR).balanceOf(address(uniswapV4Exposed)), 0); + assertTrue(USDT.balanceOf(BOB) == amountOut && amountOut > 0); } }