Skip to content

Commit

Permalink
Use router balances for #mint/#increaseLiquidity (#43)
Browse files Browse the repository at this point in the history
* add mint/increaseLiquidity

* snaps

* check pool balances
  • Loading branch information
NoahZinsmeister authored Dec 14, 2021
1 parent 93f5f5e commit d5d37fb
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 35 deletions.
73 changes: 61 additions & 12 deletions contracts/base/ApproveAndCall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol';

import '../interfaces/IApproveAndCall.sol';
import './ImmutableState.sol';
Expand All @@ -21,47 +21,46 @@ abstract contract ApproveAndCall is IApproveAndCall, ImmutableState {
/// @inheritdoc IApproveAndCall
function getApprovalType(address token, uint256 amount) external override returns (ApprovalType) {
// check existing approval
uint256 approval = IERC20(token).allowance(address(this), positionManager);
if (approval >= amount) return ApprovalType.NOT_REQUIRED;
if (IERC20(token).allowance(address(this), positionManager) >= amount) return ApprovalType.NOT_REQUIRED;

// try type(uint256).max / type(uint256).max - 1
if (tryApprove(token, type(uint256).max)) return ApprovalType.MAX;
if (tryApprove(token, type(uint256).max - 1)) return ApprovalType.MAX_MINUS_ONE;

// set approval to 0 (must succeed)
require(tryApprove(token, 0), 'A0');
require(tryApprove(token, 0));

// try type(uint256).max / type(uint256).max - 1
if (tryApprove(token, type(uint256).max)) return ApprovalType.ZERO_THEN_MAX;
if (tryApprove(token, type(uint256).max - 1)) return ApprovalType.ZERO_THEN_MAX_MINUS_ONE;

revert('APP');
revert();
}

/// @inheritdoc IApproveAndCall
function approveMax(address token) external payable override {
TransferHelper.safeApprove(token, positionManager, type(uint256).max);
require(tryApprove(token, type(uint256).max));
}

/// @inheritdoc IApproveAndCall
function approveMaxMinusOne(address token) external payable override {
TransferHelper.safeApprove(token, positionManager, type(uint256).max - 1);
require(tryApprove(token, type(uint256).max - 1));
}

/// @inheritdoc IApproveAndCall
function approveZeroThenMax(address token) external payable override {
TransferHelper.safeApprove(token, positionManager, 0);
TransferHelper.safeApprove(token, positionManager, type(uint256).max);
require(tryApprove(token, 0));
require(tryApprove(token, type(uint256).max));
}

/// @inheritdoc IApproveAndCall
function approveZeroThenMaxMinusOne(address token) external payable override {
TransferHelper.safeApprove(token, positionManager, 0);
TransferHelper.safeApprove(token, positionManager, type(uint256).max - 1);
require(tryApprove(token, 0));
require(tryApprove(token, type(uint256).max - 1));
}

/// @inheritdoc IApproveAndCall
function callPositionManager(bytes calldata data) external payable override returns (bytes memory result) {
function callPositionManager(bytes memory data) public payable override returns (bytes memory result) {
bool success;
(success, result) = positionManager.call(data);

Expand All @@ -74,4 +73,54 @@ abstract contract ApproveAndCall is IApproveAndCall, ImmutableState {
revert(abi.decode(result, (string)));
}
}

function balanceOf(address token) private view returns (uint256) {
return IERC20(token).balanceOf(address(this));
}

/// @inheritdoc IApproveAndCall
function mint(MintParams calldata params) external payable override returns (bytes memory result) {
return
callPositionManager(
abi.encodeWithSelector(
INonfungiblePositionManager.mint.selector,
INonfungiblePositionManager.MintParams({
token0: params.token0,
token1: params.token1,
fee: params.fee,
tickLower: params.tickLower,
tickUpper: params.tickUpper,
amount0Desired: balanceOf(params.token0),
amount1Desired: balanceOf(params.token1),
amount0Min: params.amount0Min,
amount1Min: params.amount1Min,
recipient: params.recipient,
deadline: type(uint256).max // deadline should be checked via multicall
})
)
);
}

/// @inheritdoc IApproveAndCall
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
override
returns (bytes memory result)
{
return
callPositionManager(
abi.encodeWithSelector(
INonfungiblePositionManager.increaseLiquidity.selector,
INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: params.tokenId,
amount0Desired: balanceOf(params.token0),
amount1Desired: balanceOf(params.token1),
amount0Min: params.amount0Min,
amount1Min: params.amount1Min,
deadline: type(uint256).max // deadline should be checked via multicall
})
)
);
}
}
31 changes: 30 additions & 1 deletion contracts/interfaces/IApproveAndCall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,34 @@ interface IApproveAndCall {
/// @notice Calls the position manager with arbitrary calldata
/// @param data Calldata to pass along to the position manager
/// @return result The result from the call
function callPositionManager(bytes calldata data) external payable returns (bytes memory result);
function callPositionManager(bytes memory data) external payable returns (bytes memory result);

struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
}

/// @notice Calls the position manager's mint function
/// @param params Calldata to pass along to the position manager
/// @return result The result from the call
function mint(MintParams calldata params) external payable returns (bytes memory result);

struct IncreaseLiquidityParams {
address token0;
address token1;
uint256 tokenId;
uint256 amount0Min;
uint256 amount1Min;
}

/// @notice Calls the position manager's increaseLiquidity function
/// @param params Calldata to pass along to the position manager
/// @return result The result from the call
function increaseLiquidity(IncreaseLiquidityParams calldata params) external payable returns (bytes memory result);
}
4 changes: 2 additions & 2 deletions contracts/libraries/UniswapV2Library.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ library UniswapV2Library {
uint256 reserveOut
) internal pure returns (uint256 amountOut) {
require(amountIn > 0, 'INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY');
require(reserveIn > 0 && reserveOut > 0);
uint256 amountInWithFee = amountIn.mul(997);
uint256 numerator = amountInWithFee.mul(reserveOut);
uint256 denominator = reserveIn.mul(1000).add(amountInWithFee);
Expand All @@ -67,7 +67,7 @@ library UniswapV2Library {
uint256 reserveOut
) internal pure returns (uint256 amountIn) {
require(amountOut > 0, 'INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY');
require(reserveIn > 0 && reserveOut > 0);
uint256 numerator = reserveIn.mul(amountOut).mul(1000);
uint256 denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
Expand Down
65 changes: 63 additions & 2 deletions test/ApproveAndCall.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,27 @@ describe('ApproveAndCall', function () {
let trader: Wallet

const swapRouterFixture: Fixture<{
factory: Contract
router: MockTimeSwapRouter02
nft: Contract
tokens: [TestERC20, TestERC20, TestERC20]
}> = async (wallets, provider) => {
const { router, tokens, nft } = await completeFixture(wallets, provider)
const { factory, router, tokens, nft } = await completeFixture(wallets, provider)

// approve & fund wallets
for (const token of tokens) {
await token.approve(nft.address, constants.MaxUint256)
}

return {
factory,
router,
tokens,
nft,
}
}

let factory: Contract
let router: MockTimeSwapRouter02
let nft: Contract
let tokens: [TestERC20, TestERC20, TestERC20]
Expand All @@ -63,7 +66,7 @@ describe('ApproveAndCall', function () {
})

beforeEach('load fixture', async () => {
;({ router, tokens, nft } = await loadFixture(swapRouterFixture))
;({ factory, router, tokens, nft } = await loadFixture(swapRouterFixture))
})

describe('swap and add', () => {
Expand Down Expand Up @@ -135,6 +138,64 @@ describe('ApproveAndCall', function () {
})
})

it('#mint and #increaseLiquidity', async () => {
await createPool(tokens[0].address, tokens[1].address)
const pool = await factory.getPool(tokens[0].address, tokens[1].address, FeeAmount.MEDIUM)

// approve in advance
await router.approveMax(tokens[0].address)
await router.approveMax(tokens[1].address)

// send dummy amount of tokens to the pair in advance
const amount = 1000
await tokens[0].transfer(router.address, amount)
await tokens[1].transfer(router.address, amount)
expect((await tokens[0].balanceOf(router.address)).toNumber()).to.be.eq(amount)
expect((await tokens[1].balanceOf(router.address)).toNumber()).to.be.eq(amount)

let poolBalance0Before = await tokens[0].balanceOf(pool)
let poolBalance1Before = await tokens[1].balanceOf(pool)

// perform the mint
await router.mint({
token0: tokens[0].address,
token1: tokens[1].address,
fee: FeeAmount.MEDIUM,
tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
recipient: trader.address,
amount0Min: 0,
amount1Min: 0,
})

expect((await tokens[0].balanceOf(router.address)).toNumber()).to.be.eq(0)
expect((await tokens[1].balanceOf(router.address)).toNumber()).to.be.eq(0)
expect((await tokens[0].balanceOf(pool)).toNumber()).to.be.eq(poolBalance0Before.toNumber() + amount)
expect((await tokens[1].balanceOf(pool)).toNumber()).to.be.eq(poolBalance1Before.toNumber() + amount)

expect((await nft.balanceOf(trader.address)).toNumber()).to.be.eq(1)

// send more tokens
await tokens[0].transfer(router.address, amount)
await tokens[1].transfer(router.address, amount)

// perform the increaseLiquidity
await router.increaseLiquidity({
token0: tokens[0].address,
token1: tokens[1].address,
tokenId: 2,
amount0Min: 0,
amount1Min: 0,
})

expect((await tokens[0].balanceOf(router.address)).toNumber()).to.be.eq(0)
expect((await tokens[1].balanceOf(router.address)).toNumber()).to.be.eq(0)
expect((await tokens[0].balanceOf(pool)).toNumber()).to.be.eq(poolBalance0Before.toNumber() + amount * 2)
expect((await tokens[1].balanceOf(pool)).toNumber()).to.be.eq(poolBalance1Before.toNumber() + amount * 2)

expect((await nft.balanceOf(trader.address)).toNumber()).to.be.eq(1)
})

describe('single-asset add', () => {
beforeEach('create 0-1 pool', async () => {
await createPool(tokens[0].address, tokens[1].address)
Expand Down
34 changes: 17 additions & 17 deletions test/__snapshots__/SwapRouter.gas.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SwapRouter gas tests #exactInput 0 -> 1 -> 2 1`] = `175438`;
exports[`SwapRouter gas tests #exactInput 0 -> 1 -> 2 1`] = `175314`;

exports[`SwapRouter gas tests #exactInput 0 -> 1 1`] = `110582`;
exports[`SwapRouter gas tests #exactInput 0 -> 1 1`] = `110507`;

exports[`SwapRouter gas tests #exactInput 0 -> 1 minimal 1`] = `98059`;

exports[`SwapRouter gas tests #exactInput 0 -> WETH9 1`] = `127476`;
exports[`SwapRouter gas tests #exactInput 0 -> WETH9 1`] = `127401`;

exports[`SwapRouter gas tests #exactInput 2 trades (directly to sender) 1`] = `179143`;
exports[`SwapRouter gas tests #exactInput 2 trades (directly to sender) 1`] = `178993`;

exports[`SwapRouter gas tests #exactInput 2 trades (via router) 1`] = `188671`;
exports[`SwapRouter gas tests #exactInput 2 trades (via router) 1`] = `188499`;

exports[`SwapRouter gas tests #exactInput 3 trades (directly to sender) 1`] = `257954`;
exports[`SwapRouter gas tests #exactInput 3 trades (directly to sender) 1`] = `257729`;

exports[`SwapRouter gas tests #exactInput WETH9 -> 0 1`] = `108907`;
exports[`SwapRouter gas tests #exactInput WETH9 -> 0 1`] = `108832`;

exports[`SwapRouter gas tests #exactInputSingle 0 -> 1 1`] = `109863`;
exports[`SwapRouter gas tests #exactInputSingle 0 -> 1 1`] = `109838`;

exports[`SwapRouter gas tests #exactInputSingle 0 -> WETH9 1`] = `126757`;
exports[`SwapRouter gas tests #exactInputSingle 0 -> WETH9 1`] = `126732`;

exports[`SwapRouter gas tests #exactInputSingle WETH9 -> 0 1`] = `108188`;
exports[`SwapRouter gas tests #exactInputSingle WETH9 -> 0 1`] = `108163`;

exports[`SwapRouter gas tests #exactOutput 0 -> 1 -> 2 1`] = `169367`;
exports[`SwapRouter gas tests #exactOutput 0 -> 1 -> 2 1`] = `169280`;

exports[`SwapRouter gas tests #exactOutput 0 -> 1 1`] = `111738`;
exports[`SwapRouter gas tests #exactOutput 0 -> 1 1`] = `111704`;

exports[`SwapRouter gas tests #exactOutput 0 -> WETH9 1`] = `128644`;
exports[`SwapRouter gas tests #exactOutput 0 -> WETH9 1`] = `128610`;

exports[`SwapRouter gas tests #exactOutput WETH9 -> 0 1`] = `119740`;
exports[`SwapRouter gas tests #exactOutput WETH9 -> 0 1`] = `119706`;

exports[`SwapRouter gas tests #exactOutputSingle 0 -> 1 1`] = `111895`;
exports[`SwapRouter gas tests #exactOutputSingle 0 -> 1 1`] = `111838`;

exports[`SwapRouter gas tests #exactOutputSingle 0 -> WETH9 1`] = `128801`;
exports[`SwapRouter gas tests #exactOutputSingle 0 -> WETH9 1`] = `128744`;

exports[`SwapRouter gas tests #exactOutputSingle WETH9 -> 0 1`] = `112684`;
exports[`SwapRouter gas tests #exactOutputSingle WETH9 -> 0 1`] = `112627`;
2 changes: 1 addition & 1 deletion test/__snapshots__/SwapRouter.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SwapRouter bytecode size 1`] = `24288`;
exports[`SwapRouter bytecode size 1`] = `24563`;

0 comments on commit d5d37fb

Please sign in to comment.