Files

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 {}
}