Skip to content

Commit

Permalink
Swap helper in Deployers (Uniswap#443)
Browse files Browse the repository at this point in the history
* helper function for a simple swap

* set currencies when they are deployed and approved

* swap handles native input in some cases; swapNativeInput to handle all native input cases; added tests for each scenario

---------

Co-authored-by: Alice <[email protected]>
  • Loading branch information
saucepoint and hensha256 authored Feb 12, 2024
1 parent 0147af8 commit b25d093
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 2 deletions.
57 changes: 55 additions & 2 deletions test/utils/Deployers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {PoolManager} from "../../src/PoolManager.sol";
import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol";
import {FeeLibrary} from "../../src/libraries/FeeLibrary.sol";
import {PoolKey} from "../../src/types/PoolKey.sol";
import {BalanceDelta} from "../../src/types/BalanceDelta.sol";
import {TickMath} from "../../src/libraries/TickMath.sol";
import {Constants} from "../utils/Constants.sol";
import {SortTokens} from "./SortTokens.sol";
import {PoolModifyLiquidityTest} from "../../src/test/PoolModifyLiquidityTest.sol";
Expand All @@ -29,6 +31,7 @@ import {
contract Deployers {
using FeeLibrary for uint24;
using PoolIdLibrary for PoolKey;
using CurrencyLibrary for Currency;

// Helpful test constants
bytes constant ZERO_BYTES = new bytes(0);
Expand All @@ -38,6 +41,9 @@ contract Deployers {
uint160 constant SQRT_RATIO_1_4 = Constants.SQRT_RATIO_1_4;
uint160 constant SQRT_RATIO_4_1 = Constants.SQRT_RATIO_4_1;

uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1;
uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1;

IPoolManager.ModifyLiquidityParams public LIQ_PARAMS =
IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18});
IPoolManager.ModifyLiquidityParams public REMOVE_LIQ_PARAMS =
Expand Down Expand Up @@ -102,7 +108,8 @@ contract Deployers {
tokens[1].approve(toApprove[i], Constants.MAX_UINT256);
}

return SortTokens.sort(tokens[0], tokens[1]);
(currency0, currency1) = SortTokens.sort(tokens[0], tokens[1]);
return (currency0, currency1);
}

function deployTokens(uint8 count, uint256 totalSupply) internal returns (MockERC20[] memory tokens) {
Expand Down Expand Up @@ -155,7 +162,7 @@ contract Deployers {
function initializeManagerRoutersAndPoolsWithLiq(IHooks hooks) internal {
deployFreshManagerAndRouters();
// sets the global currencyies and key
(currency0, currency1) = deployMintAndApprove2Currencies();
deployMintAndApprove2Currencies();
(key,) = initPoolAndAddLiquidity(currency0, currency1, hooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES);
(nativeKey,) = initPoolAndAddLiquidityETH(
CurrencyLibrary.NATIVE, currency1, hooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES, 1 ether
Expand All @@ -166,6 +173,52 @@ contract Deployers {
uninitializedNativeKey.fee = 100;
}

/// @notice Helper function for a simple ERC20 swaps that allows for unlimited price impact
function swap(PoolKey memory _key, bool zeroForOne, int256 amountSpecified, bytes memory hookData)
internal
returns (BalanceDelta)
{
// allow native input for exact-input, guide users to the `swapNativeInput` function
bool isNativeInput = zeroForOne && _key.currency0.isNative();
if (isNativeInput) require(0 < amountSpecified, "Use swapNativeInput() for native-token exact-output swaps");

uint256 value = isNativeInput ? uint256(amountSpecified) : 0;

return swapRouter.swap{value: value}(
_key,
IPoolManager.SwapParams({
zeroForOne: zeroForOne,
amountSpecified: amountSpecified,
sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT
}),
PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true, currencyAlreadySent: false}),
hookData
);
}

/// @notice Helper function for a simple Native-token swap that allows for unlimited price impact
function swapNativeInput(
PoolKey memory _key,
bool zeroForOne,
int256 amountSpecified,
bytes memory hookData,
uint256 msgValue
) internal returns (BalanceDelta) {
require(_key.currency0.isNative(), "currency0 is not native. Use swap() instead");
if (zeroForOne == false) require(msgValue == 0, "msgValue must be 0 for oneForZero swaps");

return swapRouter.swap{value: msgValue}(
_key,
IPoolManager.SwapParams({
zeroForOne: zeroForOne,
amountSpecified: amountSpecified,
sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT
}),
PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true, currencyAlreadySent: false}),
hookData
);
}

// to receive refunds of spare eth from test helpers
receive() external payable {}
}
138 changes: 138 additions & 0 deletions test/utils/SwapHelper.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";
import {Test} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";
import {Hooks} from "../../src/libraries/Hooks.sol";
import {FeeLibrary} from "../../src/libraries/FeeLibrary.sol";
import {MockHooks} from "../../src/test/MockHooks.sol";
import {IPoolManager} from "../../src/interfaces/IPoolManager.sol";
import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol";
import {IHooks} from "../../src/interfaces/IHooks.sol";
import {Currency} from "../../src/types/Currency.sol";
import {PoolManager} from "../../src/PoolManager.sol";
import {PoolSwapTest} from "../../src/test/PoolSwapTest.sol";
import {PoolDonateTest} from "../../src/test/PoolDonateTest.sol";
import {Deployers} from "./Deployers.sol";
import {Fees} from "../../src/Fees.sol";
import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol";
import {PoolKey} from "../../src/types/PoolKey.sol";
import {AccessLockHook} from "../../src/test/AccessLockHook.sol";
import {IERC20Minimal} from "../../src/interfaces/external/IERC20Minimal.sol";
import {BalanceDelta} from "../../src/types/BalanceDelta.sol";

/// @notice Testing Deployers.swap() and Deployers.swapNativeInput()
contract SwapHelperTest is Test, Deployers, GasSnapshot {
using PoolIdLibrary for PoolKey;
using Hooks for IHooks;

address payable ALL_HOOKS_ADDRESS = payable(0xfFf0000000000000000000000000000000000000);
MockHooks mockHooks;

function setUp() public {
MockHooks impl = new MockHooks();
vm.etch(ALL_HOOKS_ADDRESS, address(impl).code);
mockHooks = MockHooks(ALL_HOOKS_ADDRESS);

initializeManagerRoutersAndPoolsWithLiq(mockHooks);
}

// --- Deployers.swap() tests --- //
function test_swap_helper_zeroForOne_exactInput() public {
int256 amountSpecified = 100;
BalanceDelta result = swap(key, true, amountSpecified, ZERO_BYTES);
assertEq(int256(result.amount0()), amountSpecified);
}

function test_swap_helper_zeroForOne_exactOutput() public {
int256 amountSpecified = -100;
BalanceDelta result = swap(key, true, amountSpecified, ZERO_BYTES);
assertEq(int256(result.amount1()), amountSpecified);
}

function test_swap_helper_oneForZero_exactInput() public {
int256 amountSpecified = 100;
BalanceDelta result = swap(key, false, amountSpecified, ZERO_BYTES);
assertEq(int256(result.amount1()), amountSpecified);
}

function test_swap_helper_oneForZero_exactOutput() public {
int256 amountSpecified = -100;
BalanceDelta result = swap(key, false, amountSpecified, ZERO_BYTES);
assertEq(int256(result.amount0()), amountSpecified);
}

function test_swap_helper_native_zeroForOne_exactInput() public {
int256 amountSpecified = 100;
BalanceDelta result = swap(nativeKey, true, amountSpecified, ZERO_BYTES);
assertEq(int256(result.amount0()), amountSpecified);
}

function test_swap_helper_native_zeroForOne_exactOutput() public {
int256 amountSpecified = -100;
vm.expectRevert();
swap(nativeKey, true, amountSpecified, ZERO_BYTES);
}

function test_swap_helper_native_oneForZero_exactInput() public {
int256 amountSpecified = 100;
BalanceDelta result = swap(nativeKey, false, amountSpecified, ZERO_BYTES);
assertEq(int256(result.amount1()), amountSpecified);
}

function test_swap_helper_native_oneForZero_exactOutput() public {
int256 amountSpecified = -100;
BalanceDelta result = swap(nativeKey, false, amountSpecified, ZERO_BYTES);
assertEq(int256(result.amount0()), amountSpecified);
}

// --- Deployers.swapNativeInput() tests --- //
function test_swapNativeInput_helper_zeroForOne_exactInput() public {
int256 amountSpecified = 100;
BalanceDelta result = swapNativeInput(nativeKey, true, amountSpecified, ZERO_BYTES, 100 wei);
assertEq(int256(result.amount0()), amountSpecified);
}

function test_swapNativeInput_helper_zeroForOne_exactOutput() public {
int256 amountSpecified = -100;
BalanceDelta result = swapNativeInput(nativeKey, true, amountSpecified, ZERO_BYTES, 200 wei); // overpay
assertEq(int256(result.amount1()), amountSpecified);
}

function test_swapNativeInput_helper_oneForZero_exactInput() public {
int256 amountSpecified = 100;
BalanceDelta result = swapNativeInput(nativeKey, false, amountSpecified, ZERO_BYTES, 0 wei);
assertEq(int256(result.amount1()), amountSpecified);
}

function test_swapNativeInput_helper_oneForZero_exactOutput() public {
int256 amountSpecified = -100;
BalanceDelta result = swapNativeInput(nativeKey, false, amountSpecified, ZERO_BYTES, 0 wei);
assertEq(int256(result.amount0()), amountSpecified);
}

function test_swapNativeInput_helper_nonnative_zeroForOne_exactInput() public {
int256 amountSpecified = 100;
vm.expectRevert();
swapNativeInput(key, true, amountSpecified, ZERO_BYTES, 0 wei);
}

function test_swapNativeInput_helper_nonnative_zeroForOne_exactOutput() public {
int256 amountSpecified = -100;
vm.expectRevert();
swapNativeInput(key, true, amountSpecified, ZERO_BYTES, 0 wei);
}

function test_swapNativeInput_helper_nonnative_oneForZero_exactInput() public {
int256 amountSpecified = 100;
vm.expectRevert();
swapNativeInput(key, false, amountSpecified, ZERO_BYTES, 0 wei);
}

function test_swapNativeInput_helper_nonnative_oneForZero_exactOutput() public {
int256 amountSpecified = -100;
vm.expectRevert();
swapNativeInput(key, false, amountSpecified, ZERO_BYTES, 0 wei);
}
}

0 comments on commit b25d093

Please sign in to comment.