mirror of
https://github.com/lux-amm/v4-periphery.git
synced 2026-10-30 20:54:39 +00:00
929 lines
37 KiB
Solidity
929 lines
37 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.24;
|
|
|
|
import "forge-std/Test.sol";
|
|
import {PoolManager} from "@luxamm/v4-core/src/PoolManager.sol";
|
|
import {IPoolManager} from "@luxamm/v4-core/src/interfaces/IPoolManager.sol";
|
|
import {IHooks} from "@luxamm/v4-core/src/interfaces/IHooks.sol";
|
|
import {Currency, CurrencyLibrary} from "@luxamm/v4-core/src/types/Currency.sol";
|
|
import {PoolId} from "@luxamm/v4-core/src/types/PoolId.sol";
|
|
import {PoolKey} from "@luxamm/v4-core/src/types/PoolKey.sol";
|
|
import {LiquidityAmounts} from "@luxamm/v4-core/test/utils/LiquidityAmounts.sol";
|
|
import {TickMath} from "@luxamm/v4-core/src/libraries/TickMath.sol";
|
|
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
|
|
import {StateLibrary} from "@luxamm/v4-core/src/libraries/StateLibrary.sol";
|
|
|
|
import {IERC20} from "forge-std/interfaces/IERC20.sol";
|
|
|
|
import {IPositionManager, IPoolInitializer_v4} from "../../src/interfaces/IPositionManager.sol";
|
|
import {Actions} from "../../src/libraries/Actions.sol";
|
|
import {PositionConfig} from "../shared/PositionConfig.sol";
|
|
import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol";
|
|
import {Planner, Plan} from "../shared/Planner.sol";
|
|
import {PosmTestSetup} from "../shared/PosmTestSetup.sol";
|
|
import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
|
|
import {MockSubscriber} from "../mocks/MockSubscriber.sol";
|
|
|
|
contract PosMGasTest is Test, PosmTestSetup {
|
|
using FixedPointMathLib for uint256;
|
|
|
|
PoolId poolId;
|
|
address alice;
|
|
uint256 alicePK;
|
|
address bob;
|
|
uint256 bobPK;
|
|
|
|
// expresses the fee as a wad (i.e. 3000 = 0.003e18 = 0.30%)
|
|
uint256 FEE_WAD;
|
|
|
|
PositionConfig config;
|
|
PositionConfig configNative;
|
|
|
|
MockSubscriber sub;
|
|
|
|
function setUp() public {
|
|
(alice, alicePK) = makeAddrAndKey("ALICE");
|
|
(bob, bobPK) = makeAddrAndKey("BOB");
|
|
|
|
deployFreshManagerAndRouters();
|
|
deployMintAndApprove2Currencies();
|
|
|
|
(key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1);
|
|
(nativeKey,) = initPool(CurrencyLibrary.ADDRESS_ZERO, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1);
|
|
FEE_WAD = uint256(key.fee).mulDivDown(FixedPointMathLib.WAD, 1_000_000);
|
|
|
|
// Requires currency0 and currency1 to be set in base Deployers contract.
|
|
deployAndApprovePosm(manager);
|
|
|
|
// Give tokens to Alice and Bob.
|
|
seedBalance(alice);
|
|
seedBalance(bob);
|
|
|
|
// Approve posm for Alice and bob.
|
|
approvePosmFor(alice);
|
|
approvePosmFor(bob);
|
|
|
|
// define a reusable range
|
|
config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300});
|
|
configNative = PositionConfig({poolKey: nativeKey, tickLower: -300, tickUpper: 300});
|
|
|
|
sub = new MockSubscriber(lpm);
|
|
}
|
|
|
|
function test_posm_initcodeHash() public {
|
|
vm.snapshotValue(
|
|
"position manager initcode hash (without constructor params, as uint256)",
|
|
uint256(keccak256(abi.encodePacked(vm.getCode("PositionManager.sol:PositionManager"))))
|
|
);
|
|
}
|
|
|
|
function test_bytecodeSize_positionManager() public {
|
|
vm.snapshotValue("positionManager bytecode size", address(lpm).code.length);
|
|
}
|
|
|
|
function test_gas_mint_withClose() public {
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
10_000 ether,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_withClose");
|
|
}
|
|
|
|
function test_gas_mint_withSettlePair() public {
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
10_000 ether,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
address(this),
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_withSettlePair");
|
|
}
|
|
|
|
function test_gas_mint_differentRanges() public {
|
|
// Explicitly mint to a new range on the same pool.
|
|
PositionConfig memory bob_mint = PositionConfig({poolKey: key, tickLower: 0, tickUpper: 60});
|
|
vm.startPrank(bob);
|
|
mint(bob_mint, 10_000 ether, address(bob), ZERO_BYTES);
|
|
vm.stopPrank();
|
|
// Mint to a diff config, diff user.
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
10_000 ether,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_warmedPool_differentRange");
|
|
}
|
|
|
|
function test_gas_mint_sameTickLower() public {
|
|
// Explicitly mint to range whos tickLower is the same.
|
|
PositionConfig memory bob_mint = PositionConfig({poolKey: key, tickLower: -300, tickUpper: -60});
|
|
vm.startPrank(bob);
|
|
mint(bob_mint, 10_000 ether, address(bob), ZERO_BYTES);
|
|
vm.stopPrank();
|
|
// Mint to a diff config, diff user.
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
10_000 ether,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_onSameTickLower");
|
|
}
|
|
|
|
function test_gas_mint_sameTickUpper() public {
|
|
// Explicitly mint to range whos tickUpperis the same.
|
|
PositionConfig memory bob_mint = PositionConfig({poolKey: key, tickLower: 60, tickUpper: 300});
|
|
vm.startPrank(bob);
|
|
mint(bob_mint, 10_000 ether, address(bob), ZERO_BYTES);
|
|
vm.stopPrank();
|
|
// Mint to a diff config, diff user.
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
10_000 ether,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_onSameTickUpper");
|
|
}
|
|
|
|
function test_gas_increaseLiquidity_erc20_withClose() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_increaseLiquidity_erc20_withClose");
|
|
}
|
|
|
|
function test_gas_increaseLiquidity_erc20_withSettlePair() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, address(this), ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_increaseLiquidity_erc20_withSettlePair");
|
|
}
|
|
|
|
function test_gas_autocompound_exactUnclaimedFees() public {
|
|
// Alice and Bob provide liquidity on the range
|
|
// Alice uses her exact fees to increase liquidity (compounding)
|
|
|
|
uint256 liquidityAlice = 3_000e18;
|
|
uint256 liquidityBob = 1_000e18;
|
|
|
|
// alice provides liquidity
|
|
vm.startPrank(alice);
|
|
uint256 tokenIdAlice = lpm.nextTokenId();
|
|
mint(config, liquidityAlice, alice, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// bob provides liquidity
|
|
vm.startPrank(bob);
|
|
mint(config, liquidityBob, bob, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// donate to create fees
|
|
uint256 amountDonate = 0.2e18;
|
|
donateRouter.donate(key, amountDonate, amountDonate, ZERO_BYTES);
|
|
|
|
// alice uses her exact fees to increase liquidity
|
|
uint256 tokensOwedAlice = amountDonate.mulDivDown(liquidityAlice, liquidityAlice + liquidityBob) - 1;
|
|
|
|
(uint160 sqrtPriceX96,,,) = StateLibrary.getSlot0(manager, config.poolKey.toId());
|
|
uint256 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts(
|
|
sqrtPriceX96,
|
|
TickMath.getSqrtPriceAtTick(config.tickLower),
|
|
TickMath.getSqrtPriceAtTick(config.tickUpper),
|
|
tokensOwedAlice,
|
|
tokensOwedAlice
|
|
);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
// because its a perfect autocompound, the delta is exactly 0 and we dont need to "close" deltas
|
|
bytes memory calls = planner.encode();
|
|
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_increase_autocompoundExactUnclaimedFees");
|
|
}
|
|
|
|
function test_gas_autocompound_clearExcess() public {
|
|
// Alice and Bob provide liquidity on the range
|
|
// Alice uses her exact fees to increase liquidity (compounding)
|
|
|
|
uint256 liquidityAlice = 3_000e18;
|
|
uint256 liquidityBob = 1_000e18;
|
|
|
|
// alice provides liquidity
|
|
vm.startPrank(alice);
|
|
uint256 tokenIdAlice = lpm.nextTokenId();
|
|
mint(config, liquidityAlice, alice, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// bob provides liquidity
|
|
vm.startPrank(bob);
|
|
mint(config, liquidityBob, bob, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// donate to create fees
|
|
uint256 amountDonate = 0.2e18;
|
|
donateRouter.donate(key, amountDonate, amountDonate, ZERO_BYTES);
|
|
|
|
// alice will use half of her fees to increase liquidity
|
|
uint256 halfTokensOwedAlice = (amountDonate.mulDivDown(liquidityAlice, liquidityAlice + liquidityBob) - 1) / 2;
|
|
|
|
(uint160 sqrtPriceX96,,,) = StateLibrary.getSlot0(manager, config.poolKey.toId());
|
|
uint256 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts(
|
|
sqrtPriceX96,
|
|
TickMath.getSqrtPriceAtTick(config.tickLower),
|
|
TickMath.getSqrtPriceAtTick(config.tickUpper),
|
|
halfTokensOwedAlice,
|
|
halfTokensOwedAlice
|
|
);
|
|
|
|
// Alice elects to forfeit unclaimed tokens
|
|
Plan memory planner = Planner.init();
|
|
planner.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, halfTokensOwedAlice + 1 wei));
|
|
planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, halfTokensOwedAlice + 1 wei));
|
|
bytes memory calls = planner.encode();
|
|
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_increase_autocompound_clearExcess");
|
|
}
|
|
|
|
// Autocompounding but the excess fees are taken to the user
|
|
function test_gas_autocompound_excessFeesCredit() public {
|
|
// Alice and Bob provide liquidity on the range
|
|
// Alice uses her fees to increase liquidity. Excess fees are accounted to alice
|
|
uint256 liquidityAlice = 3_000e18;
|
|
uint256 liquidityBob = 1_000e18;
|
|
|
|
// alice provides liquidity
|
|
vm.startPrank(alice);
|
|
uint256 tokenIdAlice = lpm.nextTokenId();
|
|
mint(config, liquidityAlice, alice, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// bob provides liquidity
|
|
vm.startPrank(bob);
|
|
mint(config, liquidityBob, bob, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// donate to create fees
|
|
uint256 amountDonate = 20e18;
|
|
donateRouter.donate(key, amountDonate, amountDonate, ZERO_BYTES);
|
|
|
|
// alice will use half of her fees to increase liquidity
|
|
uint256 halfTokensOwedAlice = (amountDonate.mulDivDown(liquidityAlice, liquidityAlice + liquidityBob) - 1) / 2;
|
|
|
|
(uint160 sqrtPriceX96,,,) = StateLibrary.getSlot0(manager, config.poolKey.toId());
|
|
uint256 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts(
|
|
sqrtPriceX96,
|
|
TickMath.getSqrtPriceAtTick(config.tickLower),
|
|
TickMath.getSqrtPriceAtTick(config.tickUpper),
|
|
halfTokensOwedAlice,
|
|
halfTokensOwedAlice
|
|
);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_increase_autocompoundExcessFeesCredit");
|
|
}
|
|
|
|
function test_gas_decreaseLiquidity_withClose() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_decreaseLiquidity_withClose");
|
|
}
|
|
|
|
function test_gas_decreaseLiquidity_withTakePair() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this));
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_decreaseLiquidity_withTakePair");
|
|
}
|
|
|
|
function test_gas_multicall_initialize_mint() public {
|
|
key = PoolKey({currency0: currency0, currency1: currency1, fee: 0, tickSpacing: 10, hooks: IHooks(address(0))});
|
|
|
|
// Use multicall to initialize a pool and mint liquidity
|
|
bytes[] memory calls = new bytes[](2);
|
|
calls[0] = abi.encodeWithSelector(IPoolInitializer_v4.initializePool.selector, key, SQRT_PRICE_1_1);
|
|
|
|
config = PositionConfig({
|
|
poolKey: key,
|
|
tickLower: TickMath.minUsableTick(key.tickSpacing),
|
|
tickUpper: TickMath.maxUsableTick(key.tickSpacing)
|
|
});
|
|
|
|
Plan memory planner = Planner.init();
|
|
planner.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
100e18,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
|
|
calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline);
|
|
|
|
IMulticall_v4(lpm).multicall(calls);
|
|
vm.snapshotGasLastCall("PositionManager_multicall_initialize_mint");
|
|
}
|
|
|
|
function test_gas_collect_withClose() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
// donate to create fee revenue
|
|
donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES);
|
|
|
|
// Collect by calling decrease with 0.
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_collect_withClose");
|
|
}
|
|
|
|
function test_gas_collect_withTakePair() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
// donate to create fee revenue
|
|
donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES);
|
|
|
|
// Collect by calling decrease with 0.
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this));
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_collect_withTakePair");
|
|
}
|
|
|
|
// same-range gas tests
|
|
function test_gas_sameRange_mint() public {
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
10_001 ether,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_sameRange");
|
|
}
|
|
|
|
function test_gas_sameRange_decrease() public {
|
|
// two positions of the same config, one of them decreases the entirety of the liquidity
|
|
vm.startPrank(alice);
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_decrease_sameRange_allLiquidity");
|
|
}
|
|
|
|
function test_gas_sameRange_collect() public {
|
|
// two positions of the same config, one of them collects all their fees
|
|
vm.startPrank(alice);
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
// donate to create fee revenue
|
|
donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_collect_sameRange");
|
|
}
|
|
|
|
function test_gas_burn_nonEmptyPosition_withClose() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES));
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_burn_nonEmpty_withClose");
|
|
}
|
|
|
|
function test_gas_burn_nonEmptyPosition_withTakePair() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES));
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this));
|
|
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_burn_nonEmpty_withTakePair");
|
|
}
|
|
|
|
function test_gas_burnEmpty() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
decreaseLiquidity(tokenId, config, 10_000 ether, ZERO_BYTES);
|
|
Plan memory planner = Planner.init()
|
|
.add(Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES));
|
|
|
|
// There is no need to include CLOSE commands.
|
|
bytes memory calls = planner.encode();
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_burn_empty");
|
|
}
|
|
|
|
function test_gas_decrease_burnEmpty_batch() public {
|
|
// Will be more expensive than not encoding a decrease and just encoding a burn.
|
|
// ie. check this against PositionManager_burn_nonEmpty
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
planner.add(
|
|
Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
|
|
// We must include CLOSE commands.
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_decrease_burnEmpty");
|
|
}
|
|
|
|
// TODO: ERC6909 Support.
|
|
function test_gas_increaseLiquidity_erc6909() public {}
|
|
function test_gas_decreaseLiquidity_erc6909() public {}
|
|
|
|
// Native Token Gas Tests
|
|
function test_gas_mint_native() public {
|
|
uint256 liquidityToAdd = 10_000 ether;
|
|
bytes memory calls = getMintEncoded(configNative, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
(uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity(
|
|
SQRT_PRICE_1_1,
|
|
TickMath.getSqrtPriceAtTick(configNative.tickLower),
|
|
TickMath.getSqrtPriceAtTick(configNative.tickUpper),
|
|
uint128(liquidityToAdd)
|
|
);
|
|
lpm.modifyLiquidities{value: amount0 + 1}(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_native");
|
|
}
|
|
|
|
function test_gas_mint_native_excess_withClose() public {
|
|
uint256 liquidityToAdd = 10_000 ether;
|
|
|
|
Plan memory planner = Planner.init();
|
|
planner.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
configNative.poolKey,
|
|
configNative.tickLower,
|
|
configNative.tickUpper,
|
|
liquidityToAdd,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0));
|
|
planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1));
|
|
planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER));
|
|
bytes memory calls = planner.encode();
|
|
|
|
(uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity(
|
|
SQRT_PRICE_1_1,
|
|
TickMath.getSqrtPriceAtTick(configNative.tickLower),
|
|
TickMath.getSqrtPriceAtTick(configNative.tickUpper),
|
|
uint128(liquidityToAdd)
|
|
);
|
|
// overpay on the native token
|
|
lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_nativeWithSweep_withClose");
|
|
}
|
|
|
|
function test_gas_mint_native_excess_withSettlePair() public {
|
|
uint256 liquidityToAdd = 10_000 ether;
|
|
|
|
Plan memory planner = Planner.init();
|
|
planner.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
configNative.poolKey,
|
|
configNative.tickLower,
|
|
configNative.tickUpper,
|
|
liquidityToAdd,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
address(this),
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1));
|
|
planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, address(this)));
|
|
bytes memory calls = planner.encode();
|
|
|
|
(uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity(
|
|
SQRT_PRICE_1_1,
|
|
TickMath.getSqrtPriceAtTick(configNative.tickLower),
|
|
TickMath.getSqrtPriceAtTick(configNative.tickUpper),
|
|
uint128(liquidityToAdd)
|
|
);
|
|
// overpay on the native token
|
|
lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_nativeWithSweep_withSettlePair");
|
|
}
|
|
|
|
function test_gas_increase_native() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
uint256 liquidityToAdd = 10_000 ether;
|
|
bytes memory calls = getIncreaseEncoded(tokenId, configNative, liquidityToAdd, ZERO_BYTES);
|
|
(uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity(
|
|
SQRT_PRICE_1_1,
|
|
TickMath.getSqrtPriceAtTick(configNative.tickLower),
|
|
TickMath.getSqrtPriceAtTick(configNative.tickUpper),
|
|
uint128(liquidityToAdd)
|
|
);
|
|
lpm.modifyLiquidities{value: amount0 + 1}(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_increaseLiquidity_native");
|
|
}
|
|
|
|
function test_gas_decrease_native() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
uint256 liquidityToRemove = 10_000 ether;
|
|
bytes memory calls = getDecreaseEncoded(tokenId, configNative, liquidityToRemove, ZERO_BYTES);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_decreaseLiquidity_native");
|
|
}
|
|
|
|
function test_gas_collect_native() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
// donate to create fee revenue
|
|
donateRouter.donate{value: 0.2e18}(configNative.poolKey, 0.2e18, 0.2e18, ZERO_BYTES);
|
|
|
|
bytes memory calls = getCollectEncoded(tokenId, configNative, ZERO_BYTES);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_collect_native");
|
|
}
|
|
|
|
function test_gas_burn_nonEmptyPosition_native_withClose() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES));
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey);
|
|
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_burn_nonEmpty_native_withClose");
|
|
}
|
|
|
|
function test_gas_burn_nonEmptyPosition_native_withTakePair() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES));
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(configNative.poolKey, address(this));
|
|
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_burn_nonEmpty_native_withTakePair");
|
|
}
|
|
|
|
function test_gas_burnEmpty_native() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
decreaseLiquidity(tokenId, configNative, 10_000 ether, ZERO_BYTES);
|
|
Plan memory planner = Planner.init()
|
|
.add(Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES));
|
|
|
|
// There is no need to include CLOSE commands.
|
|
bytes memory calls = planner.encode();
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_burn_empty_native");
|
|
}
|
|
|
|
function test_gas_decrease_burnEmpty_batch_native() public {
|
|
// Will be more expensive than not encoding a decrease and just encoding a burn.
|
|
// ie. check this against PositionManager_burn_nonEmpty
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init()
|
|
.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
planner.add(Actions.BURN_POSITION, abi.encode(tokenId, 0 wei, 0 wei, ZERO_BYTES));
|
|
|
|
// We must include CLOSE commands.
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_decrease_burnEmpty_native");
|
|
}
|
|
|
|
function test_gas_permit() public {
|
|
// alice permits for the first time
|
|
uint256 liquidityAlice = 1e18;
|
|
vm.startPrank(alice);
|
|
uint256 tokenIdAlice = lpm.nextTokenId();
|
|
mint(config, liquidityAlice, alice, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// alice gives operator permission to bob
|
|
uint256 nonce = 1;
|
|
bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1);
|
|
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest);
|
|
bytes memory signature = abi.encodePacked(r, s, v);
|
|
|
|
vm.prank(bob);
|
|
lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature);
|
|
vm.snapshotGasLastCall("PositionManager_permit");
|
|
}
|
|
|
|
function test_gas_permit_secondPosition() public {
|
|
// alice permits for her two tokens, benchmark the 2nd permit
|
|
uint256 liquidityAlice = 1e18;
|
|
vm.startPrank(alice);
|
|
uint256 tokenIdAlice = lpm.nextTokenId();
|
|
mint(config, liquidityAlice, alice, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// alice gives operator permission to bob
|
|
uint256 nonce = 1;
|
|
bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1);
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest);
|
|
bytes memory signature = abi.encodePacked(r, s, v);
|
|
|
|
vm.prank(bob);
|
|
lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature);
|
|
|
|
// alice creates another position
|
|
vm.startPrank(alice);
|
|
tokenIdAlice = lpm.nextTokenId();
|
|
mint(config, liquidityAlice, alice, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// alice gives operator permission to bob
|
|
nonce = 2;
|
|
digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1);
|
|
(v, r, s) = vm.sign(alicePK, digest);
|
|
signature = abi.encodePacked(r, s, v);
|
|
|
|
vm.prank(bob);
|
|
lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature);
|
|
vm.snapshotGasLastCall("PositionManager_permit_secondPosition");
|
|
}
|
|
|
|
function test_gas_permit_twice() public {
|
|
// alice permits the same token, twice
|
|
address charlie = makeAddr("CHARLIE");
|
|
|
|
uint256 liquidityAlice = 1e18;
|
|
vm.startPrank(alice);
|
|
uint256 tokenIdAlice = lpm.nextTokenId();
|
|
mint(config, liquidityAlice, alice, ZERO_BYTES);
|
|
vm.stopPrank();
|
|
|
|
// alice gives operator permission to bob
|
|
uint256 nonce = 1;
|
|
bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1);
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest);
|
|
bytes memory signature = abi.encodePacked(r, s, v);
|
|
|
|
vm.prank(bob);
|
|
lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature);
|
|
|
|
// alice gives operator permission to charlie
|
|
nonce = 2;
|
|
digest = getDigest(charlie, tokenIdAlice, nonce, block.timestamp + 1);
|
|
(v, r, s) = vm.sign(alicePK, digest);
|
|
signature = abi.encodePacked(r, s, v);
|
|
|
|
vm.prank(bob);
|
|
lpm.permit(charlie, tokenIdAlice, block.timestamp + 1, nonce, signature);
|
|
vm.snapshotGasLastCall("PositionManager_permit_twice");
|
|
}
|
|
|
|
function test_gas_mint_settleWithBalance_sweep() public {
|
|
uint256 liquidityAlice = 3_000e18;
|
|
|
|
Plan memory planner = Planner.init();
|
|
planner.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
liquidityAlice,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
alice,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false));
|
|
planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false));
|
|
planner.add(Actions.SWEEP, abi.encode(currency0, ActionConstants.MSG_SENDER));
|
|
planner.add(Actions.SWEEP, abi.encode(currency1, ActionConstants.MSG_SENDER));
|
|
|
|
currency0.transfer(address(lpm), 100e18);
|
|
currency1.transfer(address(lpm), 100e18);
|
|
|
|
bytes memory calls = planner.encode();
|
|
|
|
vm.prank(alice);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_mint_settleWithBalance_sweep");
|
|
}
|
|
|
|
// Does not encode a take pair
|
|
function test_gas_decrease_take_take() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory plan = Planner.init();
|
|
plan.add(
|
|
Actions.DECREASE_LIQUIDITY,
|
|
abi.encode(tokenId, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES)
|
|
);
|
|
bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, ActionConstants.MSG_SENDER);
|
|
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
vm.snapshotGasLastCall("PositionManager_decrease_take_take");
|
|
}
|
|
|
|
function test_gas_subscribe_unsubscribe() public {
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
|
|
vm.snapshotGasLastCall("PositionManager_subscribe");
|
|
|
|
lpm.unsubscribe(tokenId);
|
|
vm.snapshotGasLastCall("PositionManager_unsubscribe");
|
|
}
|
|
}
|