mirror of
https://github.com/lux-amm/v4-core.git
synced 2026-10-30 20:54:53 +00:00
321 lines
13 KiB
Solidity
321 lines
13 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {IHooks} from "../src/interfaces/IHooks.sol";
|
|
import {Hooks} from "../src/libraries/Hooks.sol";
|
|
import {IPoolManager} from "../src/interfaces/IPoolManager.sol";
|
|
import {IProtocolFees} from "../src/interfaces/IProtocolFees.sol";
|
|
import {PoolManager} from "../src/PoolManager.sol";
|
|
import {TickMath} from "../src/libraries/TickMath.sol";
|
|
import {Pool} from "../src/libraries/Pool.sol";
|
|
import {Deployers} from "./utils/Deployers.sol";
|
|
import {Constants} from "./utils/Constants.sol";
|
|
import {Currency, CurrencyLibrary} from "../src/types/Currency.sol";
|
|
import {MockHooks} from "../src/test/MockHooks.sol";
|
|
import {MockContract} from "../src/test/MockContract.sol";
|
|
import {EmptyTestHooks} from "../src/test/EmptyTestHooks.sol";
|
|
import {PoolKey} from "../src/types/PoolKey.sol";
|
|
import {PoolId} from "../src/types/PoolId.sol";
|
|
import {LPFeeLibrary} from "../src/libraries/LPFeeLibrary.sol";
|
|
import {ProtocolFeeLibrary} from "../src/libraries/ProtocolFeeLibrary.sol";
|
|
import {StateLibrary} from "../src/libraries/StateLibrary.sol";
|
|
|
|
contract PoolManagerInitializeTest is Test, Deployers {
|
|
using Hooks for IHooks;
|
|
using LPFeeLibrary for uint24;
|
|
using ProtocolFeeLibrary for uint24;
|
|
using StateLibrary for IPoolManager;
|
|
|
|
event Initialize(
|
|
PoolId indexed poolId,
|
|
Currency indexed currency0,
|
|
Currency indexed currency1,
|
|
uint24 fee,
|
|
int24 tickSpacing,
|
|
IHooks hooks,
|
|
uint160 sqrtPriceX96,
|
|
int24 tick
|
|
);
|
|
|
|
function setUp() public {
|
|
deployFreshManagerAndRouters();
|
|
deployMintAndApprove2Currencies();
|
|
|
|
uninitializedKey = PoolKey({
|
|
currency0: currency0,
|
|
currency1: currency1,
|
|
fee: 3000,
|
|
hooks: IHooks(Constants.ADDRESS_ZERO),
|
|
tickSpacing: 60
|
|
});
|
|
}
|
|
|
|
function test_fuzz_initialize(PoolKey memory key0, uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
// tested in Hooks.t.sol
|
|
key0.hooks = IHooks(Constants.ADDRESS_ZERO);
|
|
|
|
if (key0.tickSpacing > TickMath.MAX_TICK_SPACING) {
|
|
vm.expectRevert(abi.encodeWithSelector(IPoolManager.TickSpacingTooLarge.selector, key0.tickSpacing));
|
|
manager.initialize(key0, sqrtPriceX96);
|
|
} else if (key0.tickSpacing < TickMath.MIN_TICK_SPACING) {
|
|
vm.expectRevert(abi.encodeWithSelector(IPoolManager.TickSpacingTooSmall.selector, key0.tickSpacing));
|
|
manager.initialize(key0, sqrtPriceX96);
|
|
} else if (key0.currency0 >= key0.currency1) {
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(
|
|
IPoolManager.CurrenciesOutOfOrderOrEqual.selector, key0.currency0, key0.currency1
|
|
)
|
|
);
|
|
manager.initialize(key0, sqrtPriceX96);
|
|
} else if (!key0.hooks.isValidHookAddress(key0.fee)) {
|
|
vm.expectRevert(abi.encodeWithSelector(Hooks.HookAddressNotValid.selector, address(key0.hooks)));
|
|
manager.initialize(key0, sqrtPriceX96);
|
|
} else if ((key0.fee != LPFeeLibrary.DYNAMIC_FEE_FLAG) && (key0.fee > 1000000)) {
|
|
vm.expectRevert(abi.encodeWithSelector(LPFeeLibrary.LPFeeTooLarge.selector, key0.fee));
|
|
manager.initialize(key0, sqrtPriceX96);
|
|
} else {
|
|
int24 tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);
|
|
vm.expectEmit(true, true, true, true);
|
|
emit Initialize(
|
|
key0.toId(), key0.currency0, key0.currency1, key0.fee, key0.tickSpacing, key0.hooks, sqrtPriceX96, tick
|
|
);
|
|
manager.initialize(key0, sqrtPriceX96);
|
|
|
|
(uint160 slot0SqrtPriceX96,, uint24 slot0ProtocolFee,) = manager.getSlot0(key0.toId());
|
|
assertEq(slot0SqrtPriceX96, sqrtPriceX96);
|
|
assertEq(slot0ProtocolFee, 0);
|
|
}
|
|
}
|
|
|
|
function test_initialize_forNativeTokens(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
uninitializedKey.currency0 = CurrencyLibrary.ADDRESS_ZERO;
|
|
|
|
int24 tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);
|
|
|
|
vm.expectEmit(true, true, true, true);
|
|
emit Initialize(
|
|
uninitializedKey.toId(),
|
|
uninitializedKey.currency0,
|
|
uninitializedKey.currency1,
|
|
uninitializedKey.fee,
|
|
uninitializedKey.tickSpacing,
|
|
uninitializedKey.hooks,
|
|
sqrtPriceX96,
|
|
tick
|
|
);
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
|
|
(uint160 slot0SqrtPriceX96, int24 slot0Tick, uint24 slot0ProtocolFee,) =
|
|
manager.getSlot0(uninitializedKey.toId());
|
|
assertEq(slot0SqrtPriceX96, sqrtPriceX96);
|
|
assertEq(slot0ProtocolFee, 0);
|
|
assertEq(slot0Tick, TickMath.getTickAtSqrtPrice(sqrtPriceX96));
|
|
}
|
|
|
|
function test_initialize_succeedsWithHooks(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
address payable mockAddr = payable(address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG)));
|
|
address payable hookAddr = payable(Constants.ALL_HOOKS);
|
|
|
|
vm.etch(hookAddr, vm.getDeployedCode("EmptyTestHooks.sol:EmptyTestHooks"));
|
|
MockContract mockContract = new MockContract();
|
|
vm.etch(mockAddr, address(mockContract).code);
|
|
|
|
MockContract(mockAddr).setImplementation(hookAddr);
|
|
|
|
uninitializedKey.hooks = IHooks(mockAddr);
|
|
|
|
int24 tick = manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
(uint160 slot0SqrtPriceX96,,,) = manager.getSlot0(uninitializedKey.toId());
|
|
assertEq(slot0SqrtPriceX96, sqrtPriceX96, "sqrtPrice");
|
|
|
|
bytes32 beforeSelector = MockHooks.beforeInitialize.selector;
|
|
bytes memory beforeParams = abi.encode(address(this), uninitializedKey, sqrtPriceX96);
|
|
|
|
bytes32 afterSelector = MockHooks.afterInitialize.selector;
|
|
bytes memory afterParams = abi.encode(address(this), uninitializedKey, sqrtPriceX96, tick);
|
|
|
|
assertEq(MockContract(mockAddr).timesCalledSelector(beforeSelector), 1, "beforeSelector count");
|
|
assertTrue(MockContract(mockAddr).calledWithSelector(beforeSelector, beforeParams), "beforeSelector params");
|
|
assertEq(MockContract(mockAddr).timesCalledSelector(afterSelector), 1, "afterSelector count");
|
|
assertTrue(MockContract(mockAddr).calledWithSelector(afterSelector, afterParams), "afterSelector params");
|
|
}
|
|
|
|
function test_initialize_succeedsWithMaxTickSpacing(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
uninitializedKey.tickSpacing = TickMath.MAX_TICK_SPACING;
|
|
|
|
int24 tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);
|
|
|
|
vm.expectEmit(true, true, true, true);
|
|
emit Initialize(
|
|
uninitializedKey.toId(),
|
|
uninitializedKey.currency0,
|
|
uninitializedKey.currency1,
|
|
uninitializedKey.fee,
|
|
uninitializedKey.tickSpacing,
|
|
uninitializedKey.hooks,
|
|
sqrtPriceX96,
|
|
tick
|
|
);
|
|
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_succeedsWithEmptyHooks(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
address allHooksAddr = Constants.ALL_HOOKS;
|
|
|
|
MockHooks impl = new MockHooks();
|
|
vm.etch(allHooksAddr, address(impl).code);
|
|
MockHooks mockHooks = MockHooks(allHooksAddr);
|
|
|
|
uninitializedKey.hooks = mockHooks;
|
|
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
(uint160 slot0SqrtPriceX96,,,) = manager.getSlot0(uninitializedKey.toId());
|
|
assertEq(slot0SqrtPriceX96, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_revertsWithIdenticalTokens(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
// Both currencies are currency0
|
|
uninitializedKey.currency1 = currency0;
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(
|
|
IPoolManager.CurrenciesOutOfOrderOrEqual.selector,
|
|
Currency.unwrap(currency0),
|
|
Currency.unwrap(currency0)
|
|
)
|
|
);
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_revertsWithSameTokenCombo(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
uninitializedKey.currency1 = currency0;
|
|
uninitializedKey.currency0 = currency1;
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(
|
|
IPoolManager.CurrenciesOutOfOrderOrEqual.selector,
|
|
Currency.unwrap(currency1),
|
|
Currency.unwrap(currency0)
|
|
)
|
|
);
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_revertsWhenPoolAlreadyInitialized(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
vm.expectRevert(Pool.PoolAlreadyInitialized.selector);
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_failsWithIncorrectSelectors() public {
|
|
address hookAddr = address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG));
|
|
|
|
MockHooks impl = new MockHooks();
|
|
vm.etch(hookAddr, address(impl).code);
|
|
MockHooks mockHooks = MockHooks(hookAddr);
|
|
|
|
uninitializedKey.hooks = mockHooks;
|
|
|
|
mockHooks.setReturnValue(mockHooks.beforeInitialize.selector, bytes4(0xdeadbeef));
|
|
mockHooks.setReturnValue(mockHooks.afterInitialize.selector, bytes4(0xdeadbeef));
|
|
|
|
// Fails at beforeInitialize hook.
|
|
vm.expectRevert(Hooks.InvalidHookResponse.selector);
|
|
manager.initialize(uninitializedKey, SQRT_PRICE_1_1);
|
|
|
|
// Fail at afterInitialize hook.
|
|
mockHooks.setReturnValue(mockHooks.beforeInitialize.selector, mockHooks.beforeInitialize.selector);
|
|
vm.expectRevert(Hooks.InvalidHookResponse.selector);
|
|
manager.initialize(uninitializedKey, SQRT_PRICE_1_1);
|
|
}
|
|
|
|
function test_initialize_succeedsWithCorrectSelectors() public {
|
|
address hookAddr = address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG));
|
|
|
|
MockHooks impl = new MockHooks();
|
|
vm.etch(hookAddr, address(impl).code);
|
|
MockHooks mockHooks = MockHooks(hookAddr);
|
|
|
|
uninitializedKey.hooks = mockHooks;
|
|
|
|
mockHooks.setReturnValue(mockHooks.beforeInitialize.selector, mockHooks.beforeInitialize.selector);
|
|
mockHooks.setReturnValue(mockHooks.afterInitialize.selector, mockHooks.afterInitialize.selector);
|
|
|
|
int24 tick = TickMath.getTickAtSqrtPrice(SQRT_PRICE_1_1);
|
|
|
|
vm.expectEmit(true, true, true, true);
|
|
emit Initialize(
|
|
uninitializedKey.toId(),
|
|
uninitializedKey.currency0,
|
|
uninitializedKey.currency1,
|
|
uninitializedKey.fee,
|
|
uninitializedKey.tickSpacing,
|
|
uninitializedKey.hooks,
|
|
SQRT_PRICE_1_1,
|
|
tick
|
|
);
|
|
|
|
manager.initialize(uninitializedKey, SQRT_PRICE_1_1);
|
|
}
|
|
|
|
function test_initialize_failsIfTickSpaceTooLarge(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
uninitializedKey.tickSpacing = TickMath.MAX_TICK_SPACING + 1;
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(IPoolManager.TickSpacingTooLarge.selector, uninitializedKey.tickSpacing));
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_failsIfTickSpaceZero(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
uninitializedKey.tickSpacing = 0;
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(IPoolManager.TickSpacingTooSmall.selector, uninitializedKey.tickSpacing));
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_failsIfTickSpaceNeg(uint160 sqrtPriceX96) public {
|
|
// Assumptions tested in Pool.t.sol
|
|
sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1));
|
|
|
|
uninitializedKey.tickSpacing = -1;
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(IPoolManager.TickSpacingTooSmall.selector, uninitializedKey.tickSpacing));
|
|
manager.initialize(uninitializedKey, sqrtPriceX96);
|
|
}
|
|
|
|
function test_initialize_gas() public {
|
|
manager.initialize(uninitializedKey, SQRT_PRICE_1_1);
|
|
vm.snapshotGasLastCall("initialize");
|
|
}
|
|
}
|