mirror of
https://github.com/lux-amm/v4-periphery.git
synced 2026-10-30 20:54:39 +00:00
274 lines
11 KiB
Solidity
274 lines
11 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} 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 {BalanceDelta} from "@luxamm/v4-core/src/types/BalanceDelta.sol";
|
|
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
|
|
import {StateLibrary} from "@luxamm/v4-core/src/libraries/StateLibrary.sol";
|
|
import {TickMath} from "@luxamm/v4-core/src/libraries/TickMath.sol";
|
|
import {LiquidityAmounts} from "@luxamm/v4-core/test/utils/LiquidityAmounts.sol";
|
|
import {Position} from "@luxamm/v4-core/src/libraries/Position.sol";
|
|
|
|
import {IERC20} from "forge-std/interfaces/IERC20.sol";
|
|
import {IERC721} from "forge-std/interfaces/IERC721.sol";
|
|
|
|
import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
|
|
import {PositionConfig} from "../shared/PositionConfig.sol";
|
|
import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
|
|
import {Actions} from "../../src/libraries/Actions.sol";
|
|
|
|
import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol";
|
|
import {Planner, Plan} from "../shared/Planner.sol";
|
|
import {PosmTestSetup} from "../shared/PosmTestSetup.sol";
|
|
|
|
contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers {
|
|
using FixedPointMathLib for uint256;
|
|
using StateLibrary for IPoolManager;
|
|
|
|
PoolId poolId;
|
|
address alice = makeAddr("ALICE");
|
|
address bob = makeAddr("BOB");
|
|
|
|
PositionConfig config;
|
|
|
|
function setUp() public {
|
|
deployFreshManagerAndRouters();
|
|
deployMintAndApprove2Currencies();
|
|
|
|
// This is needed to receive return deltas from modifyLiquidity calls.
|
|
deployPosmHookSavesDelta();
|
|
|
|
(key, poolId) = initPool(currency0, currency1, IHooks(address(hook)), 3000, SQRT_PRICE_1_1);
|
|
|
|
// 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 pool position
|
|
config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300});
|
|
}
|
|
|
|
function test_fuzz_execute_increaseLiquidity_once(uint256 initialLiquidity, uint256 liquidityToAdd) public {
|
|
initialLiquidity = bound(initialLiquidity, 1e18, 1000e18);
|
|
liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18);
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, initialLiquidity, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES);
|
|
|
|
uint256 liquidity = lpm.getPositionLiquidity(tokenId);
|
|
|
|
assertEq(liquidity, initialLiquidity + liquidityToAdd);
|
|
}
|
|
|
|
function test_fuzz_execute_increaseLiquidity_twice_withClose(
|
|
uint256 initialLiquidity,
|
|
uint256 liquidityToAdd,
|
|
uint256 liquidityToAdd2
|
|
) public {
|
|
initialLiquidity = bound(initialLiquidity, 1e18, 1000e18);
|
|
liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18);
|
|
liquidityToAdd2 = bound(liquidityToAdd2, 1e18, 1000e18);
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, initialLiquidity, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init();
|
|
|
|
planner.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
planner.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenId, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
|
|
uint256 liquidity = lpm.getPositionLiquidity(tokenId);
|
|
|
|
assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2);
|
|
}
|
|
|
|
function test_fuzz_execute_increaseLiquidity_twice_withSettlePair(
|
|
uint256 initialLiquidity,
|
|
uint256 liquidityToAdd,
|
|
uint256 liquidityToAdd2
|
|
) public {
|
|
initialLiquidity = bound(initialLiquidity, 1e18, 1000e18);
|
|
liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18);
|
|
liquidityToAdd2 = bound(liquidityToAdd2, 1e18, 1000e18);
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, initialLiquidity, address(this), ZERO_BYTES);
|
|
|
|
Plan memory planner = Planner.init();
|
|
|
|
planner.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
planner.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenId, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
|
|
uint256 liquidity = lpm.getPositionLiquidity(tokenId);
|
|
|
|
assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2);
|
|
}
|
|
|
|
// this case doesnt make sense in real world usage, so it doesnt have a cool name. but its a good test case
|
|
function test_fuzz_execute_mintAndIncrease(uint256 initialLiquidity, uint256 liquidityToAdd) public {
|
|
initialLiquidity = bound(initialLiquidity, 1e18, 1000e18);
|
|
liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18);
|
|
|
|
uint256 tokenId = lpm.nextTokenId(); // assume that the .mint() produces tokenId=1, to be used in increaseLiquidity
|
|
|
|
Plan memory planner = Planner.init();
|
|
|
|
planner.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
config.poolKey,
|
|
config.tickLower,
|
|
config.tickUpper,
|
|
initialLiquidity,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
planner.add(
|
|
Actions.INCREASE_LIQUIDITY,
|
|
abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES)
|
|
);
|
|
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
|
|
uint256 liquidity = lpm.getPositionLiquidity(tokenId);
|
|
|
|
assertEq(liquidity, initialLiquidity + liquidityToAdd);
|
|
}
|
|
|
|
// rebalance: burn and mint
|
|
function test_execute_rebalance_perfect() public {
|
|
uint256 initialLiquidity = 100e18;
|
|
|
|
// mint a position on range [-300, 300]
|
|
uint256 tokenId = lpm.nextTokenId();
|
|
mint(config, initialLiquidity, ActionConstants.MSG_SENDER, ZERO_BYTES);
|
|
BalanceDelta delta = getLastDelta();
|
|
|
|
// we'll burn and mint a new position on [-60, 60]; calculate the liquidity units for the new range
|
|
PositionConfig memory newConfig = PositionConfig({poolKey: config.poolKey, tickLower: -60, tickUpper: 60});
|
|
uint128 newLiquidity = LiquidityAmounts.getLiquidityForAmounts(
|
|
SQRT_PRICE_1_1,
|
|
TickMath.getSqrtPriceAtTick(newConfig.tickLower),
|
|
TickMath.getSqrtPriceAtTick(newConfig.tickUpper),
|
|
uint128(-delta.amount0()),
|
|
uint128(-delta.amount1())
|
|
);
|
|
|
|
uint256 balance0Before = currency0.balanceOfSelf();
|
|
uint256 balance1Before = currency1.balanceOfSelf();
|
|
|
|
hook.clearDeltas(); // clear the delta so that we can check the net delta for BURN & MINT
|
|
|
|
Plan memory planner = Planner.init();
|
|
planner.add(
|
|
Actions.BURN_POSITION,
|
|
abi.encode(tokenId, uint128(-delta.amount0()) - 1 wei, uint128(-delta.amount1()) - 1 wei, ZERO_BYTES)
|
|
);
|
|
planner.add(
|
|
Actions.MINT_POSITION,
|
|
abi.encode(
|
|
newConfig.poolKey,
|
|
newConfig.tickLower,
|
|
newConfig.tickUpper,
|
|
newLiquidity,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
MAX_SLIPPAGE_INCREASE,
|
|
ActionConstants.MSG_SENDER,
|
|
ZERO_BYTES
|
|
)
|
|
);
|
|
bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey);
|
|
|
|
lpm.modifyLiquidities(calls, _deadline);
|
|
{
|
|
BalanceDelta netDelta = getNetDelta();
|
|
|
|
uint256 balance0After = currency0.balanceOfSelf();
|
|
uint256 balance1After = currency1.balanceOfSelf();
|
|
|
|
// TODO: use clear so user does not pay 1 wei
|
|
assertEq(netDelta.amount0(), -1 wei);
|
|
assertEq(netDelta.amount1(), -1 wei);
|
|
assertApproxEqAbs(balance0Before - balance0After, 0, 1 wei);
|
|
assertApproxEqAbs(balance1Before - balance1After, 0, 1 wei);
|
|
}
|
|
|
|
// old position was burned
|
|
vm.expectRevert();
|
|
IERC721(address(lpm)).ownerOf(tokenId);
|
|
|
|
{
|
|
// old position has no liquidity
|
|
uint128 liquidity = lpm.getPositionLiquidity(tokenId);
|
|
assertEq(liquidity, 0);
|
|
|
|
// new token was minted
|
|
uint256 newTokenId = lpm.nextTokenId() - 1;
|
|
assertEq(IERC721(address(lpm)).ownerOf(newTokenId), address(this));
|
|
|
|
// new token has expected liquidity
|
|
|
|
liquidity = lpm.getPositionLiquidity(newTokenId);
|
|
assertEq(liquidity, newLiquidity);
|
|
}
|
|
}
|
|
|
|
// coalesce: burn and increase
|
|
function test_execute_coalesce() public {}
|
|
// split: decrease and mint
|
|
function test_execute_split() public {}
|
|
// shift: decrease and increase
|
|
function test_execute_shift() public {}
|
|
// shard: collect and mint
|
|
function test_execute_shard() public {}
|
|
// feed: collect and increase
|
|
function test_execute_feed() public {}
|
|
|
|
// transplant: burn and mint on different keys
|
|
function test_execute_transplant() public {}
|
|
// cross-coalesce: burn and increase on different keys
|
|
function test_execute_crossCoalesce() public {}
|
|
// cross-split: decrease and mint on different keys
|
|
function test_execute_crossSplit() public {}
|
|
// cross-shift: decrease and increase on different keys
|
|
function test_execute_crossShift() public {}
|
|
// cross-shard: collect and mint on different keys
|
|
function test_execute_crossShard() public {}
|
|
// cross-feed: collect and increase on different keys
|
|
function test_execute_crossFeed() public {}
|
|
}
|