feat: Support Euler low balance single swaps (univ4)

Took 1 hour 34 minutes


Took 4 minutes
This commit is contained in:
Diana Carvalho
2025-10-09 10:26:28 +02:00
parent eb05dbcf27
commit f82ae3b92a
3 changed files with 77 additions and 20 deletions

View File

@@ -275,17 +275,12 @@ contract UniswapV4Executor is
address receiver, address receiver,
bytes calldata hookData bytes calldata hookData
) external returns (uint128) { ) external returns (uint128) {
Currency currencyIn = zeroForOne ? poolKey.currency0 : poolKey.currency1;
_settle(currencyIn, amountIn, transferType);
uint128 amountOut = _swap( uint128 amountOut = _swap(
poolKey, zeroForOne, -int256(uint256(amountIn)), hookData poolKey, zeroForOne, -int256(uint256(amountIn)), hookData
).toUint128(); ).toUint128();
Currency currencyIn = zeroForOne ? poolKey.currency0 : poolKey.currency1;
uint256 amount = _getFullDebt(currencyIn);
if (amount > amountIn) {
revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount);
}
_settle(currencyIn, amount, transferType);
Currency currencyOut = Currency currencyOut =
zeroForOne ? poolKey.currency1 : poolKey.currency0; zeroForOne ? poolKey.currency1 : poolKey.currency0;
_take(currencyOut, receiver, _mapTakeAmount(amountOut, currencyOut)); _take(currencyOut, receiver, _mapTakeAmount(amountOut, currencyOut));

View File

@@ -56,6 +56,7 @@ contract Constants is Test, BaseConstants {
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3); address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f); address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);
address ONDO_ADDR = address(0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3); address ONDO_ADDR = address(0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3);
address RLUSD_ADDR = address(0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD);
// Maverick v2 // Maverick v2
address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e; address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e;
@@ -146,6 +147,9 @@ contract Constants is Test, BaseConstants {
// Curve meta registry // Curve meta registry
address CURVE_META_REGISTRY = 0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC; address CURVE_META_REGISTRY = 0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC;
// Uniswap v4 pool manager
address POOL_MANAGER = 0x000000000004444c5dc75cB358380D2e3dE08A90;
/** /**
* @dev Deploys a dummy contract with non-empty bytecode * @dev Deploys a dummy contract with non-empty bytecode
*/ */

View File

@@ -11,8 +11,8 @@ import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
import {Test} from "../../lib/forge-std/src/Test.sol"; import {Test} from "../../lib/forge-std/src/Test.sol";
contract UniswapV4ExecutorExposed is UniswapV4Executor { contract UniswapV4ExecutorExposed is UniswapV4Executor {
constructor(IPoolManager _poolManager, address _permit2) constructor(IPoolManager _POOL_MANAGER, address _permit2)
UniswapV4Executor(_poolManager, _permit2) UniswapV4Executor(_POOL_MANAGER, _permit2)
{} {}
function decodeData(bytes calldata data) function decodeData(bytes calldata data)
@@ -41,13 +41,11 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
IERC20 USDT = IERC20(USDT_ADDR); IERC20 USDT = IERC20(USDT_ADDR);
IERC20 USDC = IERC20(USDC_ADDR); IERC20 USDC = IERC20(USDC_ADDR);
address poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
function setUp() public { function setUp() public {
uint256 forkBlock = 22689128; uint256 forkBlock = 22689128;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
uniswapV4Exposed = new UniswapV4ExecutorExposed( uniswapV4Exposed = new UniswapV4ExecutorExposed(
IPoolManager(poolManager), PERMIT2_ADDRESS IPoolManager(POOL_MANAGER), PERMIT2_ADDRESS
); );
} }
@@ -114,7 +112,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
function testSingleSwap() public { function testSingleSwap() public {
uint256 amountIn = 100 ether; uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); uint256 usdeBalanceBeforePool = USDE.balanceOf(POOL_MANAGER);
uint256 usdeBalanceBeforeSwapExecutor = uint256 usdeBalanceBeforeSwapExecutor =
USDE.balanceOf(address(uniswapV4Exposed)); USDE.balanceOf(address(uniswapV4Exposed));
@@ -138,7 +136,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
); );
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); assertEq(USDE.balanceOf(POOL_MANAGER), usdeBalanceBeforePool + amountIn);
assertEq( assertEq(
USDE.balanceOf(address(uniswapV4Exposed)), USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn usdeBalanceBeforeSwapExecutor - amountIn
@@ -152,12 +150,12 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
loadCallDataFromFile("test_encode_uniswap_v4_simple_swap"); loadCallDataFromFile("test_encode_uniswap_v4_simple_swap");
uint256 amountIn = 100 ether; uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); uint256 usdeBalanceBeforePool = USDE.balanceOf(POOL_MANAGER);
uint256 usdeBalanceBeforeSwapExecutor = uint256 usdeBalanceBeforeSwapExecutor =
USDE.balanceOf(address(uniswapV4Exposed)); USDE.balanceOf(address(uniswapV4Exposed));
uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData); uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); assertEq(USDE.balanceOf(POOL_MANAGER), usdeBalanceBeforePool + amountIn);
assertEq( assertEq(
USDE.balanceOf(ALICE), usdeBalanceBeforeSwapExecutor - amountIn USDE.balanceOf(ALICE), usdeBalanceBeforeSwapExecutor - amountIn
); );
@@ -168,7 +166,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
// USDE -> USDT -> WBTC // USDE -> USDT -> WBTC
uint256 amountIn = 100 ether; uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); uint256 usdeBalanceBeforePool = USDE.balanceOf(POOL_MANAGER);
uint256 usdeBalanceBeforeSwapExecutor = uint256 usdeBalanceBeforeSwapExecutor =
USDE.balanceOf(address(uniswapV4Exposed)); USDE.balanceOf(address(uniswapV4Exposed));
@@ -197,7 +195,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
); );
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); assertEq(USDE.balanceOf(POOL_MANAGER), usdeBalanceBeforePool + amountIn);
assertEq( assertEq(
USDE.balanceOf(address(uniswapV4Exposed)), USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn usdeBalanceBeforeSwapExecutor - amountIn
@@ -212,12 +210,12 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
uint256 amountIn = 100 ether; uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn); deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager); uint256 usdeBalanceBeforePool = USDE.balanceOf(POOL_MANAGER);
uint256 usdeBalanceBeforeSwapExecutor = uint256 usdeBalanceBeforeSwapExecutor =
USDE.balanceOf(address(uniswapV4Exposed)); USDE.balanceOf(address(uniswapV4Exposed));
uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData); uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn); assertEq(USDE.balanceOf(POOL_MANAGER), usdeBalanceBeforePool + amountIn);
assertEq( assertEq(
USDE.balanceOf(address(uniswapV4Exposed)), USDE.balanceOf(address(uniswapV4Exposed)),
usdeBalanceBeforeSwapExecutor - amountIn usdeBalanceBeforeSwapExecutor - amountIn
@@ -267,6 +265,66 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
} }
} }
contract UniswapV4ExecutorTestForEuler is Constants, TestUtils {
/* These tests are necessary because Euler works a little differently from general UniswapV4 logic.
In the previous version of the UniswapV4Executor we are only sending the user's tokens into the Pool Manager
after we call swap on it. This is ok because the Pool Manager tracks the debts and accepts everything as long
as the tokens are transfers inside of the unlock callback. However, Euler expects the funds to already be
in the Pool Manager when beforeSwap is called. This is not a problem for tokens that the Pool Manager has a
lot of, but for tokens with low balances this makes the tx fail. We need to transfer the tokens into
the Pool Manager before we call swap on it.
The only risk here is that we are assuming that the amount_in will never change. In the previous version, we
were confirming this amount with the currencyDelta of the Pool Manager. Now we pray.
*/
using SafeERC20 for IERC20;
UniswapV4ExecutorExposed uniswapV4Exposed;
IERC20 USDT = IERC20(USDT_ADDR);
IERC20 RLUSD = IERC20(RLUSD_ADDR);
function setUp() public {
uint256 forkBlock = 23535338;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
uniswapV4Exposed = new UniswapV4ExecutorExposed(
IPoolManager(POOL_MANAGER), PERMIT2_ADDRESS
);
}
function testSingleSwapEulerLowBalance() public {
uint256 amountIn = 134187695711754971245517404;
deal(RLUSD_ADDR, address(uniswapV4Exposed), amountIn);
address eulerProxy = 0xe1Ce9AF672f8854845E5474400B6ddC7AE458a10;
uint256 rlusdEulerBalanceBefore = RLUSD.balanceOf(eulerProxy);
uint256 rlusdBalanceBeforeSwapExecutor =
RLUSD.balanceOf(address(uniswapV4Exposed));
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](1);
pools[0] = UniswapV4Executor.UniswapV4Pool({
intermediaryToken: USDT_ADDR,
fee: uint24(50),
tickSpacing: int24(1)
});
bytes memory data = UniswapV4Utils.encodeExactInput(
RLUSD_ADDR,
USDT_ADDR,
true,
RestrictTransferFrom.TransferType.Transfer,
ALICE,
address(0xF87ACF8428F2f9403AAA0256A7272d6549ECa8A8),
bytes(""),
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(
RLUSD.balanceOf(eulerProxy), rlusdEulerBalanceBefore + amountIn
);
assertTrue(USDT.balanceOf(ALICE) == amountOut);
}
}
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup { contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
function testSingleSwapUSV4CallbackPermit2() public { function testSingleSwapUSV4CallbackPermit2() public {
vm.startPrank(ALICE); vm.startPrank(ALICE);