mirror of
https://github.com/lux-amm/v4-periphery.git
synced 2026-10-30 20:54:39 +00:00
176 lines
7.4 KiB
Solidity
176 lines
7.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.24;
|
|
|
|
import "forge-std/Test.sol";
|
|
import {Currency} from "@luxamm/v4-core/src/types/Currency.sol";
|
|
import {BalanceDelta} from "@luxamm/v4-core/src/types/BalanceDelta.sol";
|
|
import {IPoolManager} from "@luxamm/v4-core/src/interfaces/IPoolManager.sol";
|
|
import {Hooks} from "@luxamm/v4-core/src/libraries/Hooks.sol";
|
|
import {PoolKey} from "@luxamm/v4-core/src/types/PoolKey.sol";
|
|
import {Deployers} from "@luxamm/v4-core/test/utils/Deployers.sol";
|
|
import {IERC20} from "forge-std/interfaces/IERC20.sol";
|
|
import {LiquidityOperations} from "./LiquidityOperations.sol";
|
|
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
|
|
import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol";
|
|
import {HookSavesDelta} from "./HookSavesDelta.sol";
|
|
import {HookModifyLiquidities} from "./HookModifyLiquidities.sol";
|
|
import {Deploy, IPositionDescriptor} from "./Deploy.sol";
|
|
import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol";
|
|
import {IWETH9} from "../../src/interfaces/external/IWETH9.sol";
|
|
import {WETH} from "solmate/src/tokens/WETH.sol";
|
|
import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
|
|
import {SortTokens} from "@luxamm/v4-core/test/utils/SortTokens.sol";
|
|
import {IHooks} from "@luxamm/v4-core/src/interfaces/IHooks.sol";
|
|
import {PositionConfig} from "../shared/PositionConfig.sol";
|
|
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
|
|
|
/// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm.
|
|
contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations {
|
|
uint256 constant STARTING_USER_BALANCE = 10_000_000 ether;
|
|
|
|
IAllowanceTransfer permit2;
|
|
IPositionDescriptor public positionDescriptor;
|
|
TransparentUpgradeableProxy proxy;
|
|
IPositionDescriptor proxyAsImplementation;
|
|
HookSavesDelta hook;
|
|
address hookAddr = address(uint160(Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG));
|
|
|
|
WETH wethImpl = new WETH();
|
|
IWETH9 public _WETH9;
|
|
|
|
address governance = address(0xABCD);
|
|
|
|
HookModifyLiquidities hookModifyLiquidities;
|
|
address hookModifyLiquiditiesAddr = address(
|
|
uint160(
|
|
Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG
|
|
| Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG
|
|
)
|
|
);
|
|
|
|
PoolKey wethKey;
|
|
|
|
function deployPosmHookSavesDelta() public {
|
|
HookSavesDelta impl = new HookSavesDelta();
|
|
vm.etch(hookAddr, address(impl).code);
|
|
hook = HookSavesDelta(hookAddr);
|
|
}
|
|
|
|
/// @dev deploys a special test hook where beforeSwap hookData is used to modify liquidity
|
|
function deployPosmHookModifyLiquidities() public {
|
|
HookModifyLiquidities impl = new HookModifyLiquidities();
|
|
vm.etch(hookModifyLiquiditiesAddr, address(impl).code);
|
|
hookModifyLiquidities = HookModifyLiquidities(hookModifyLiquiditiesAddr);
|
|
|
|
// set posm address since constructor args are not easily copied by vm.etch
|
|
hookModifyLiquidities.setAddresses(lpm, permit2);
|
|
}
|
|
|
|
function deployAndApprovePosm(IPoolManager poolManager) public {
|
|
deployPosm(poolManager);
|
|
approvePosm();
|
|
}
|
|
|
|
function deployPosm(IPoolManager poolManager) internal {
|
|
// We use deployPermit2() to prevent having to use via-ir in this repository.
|
|
permit2 = IAllowanceTransfer(deployPermit2());
|
|
_WETH9 = deployWETH();
|
|
proxyAsImplementation = deployDescriptor(poolManager, "ETH");
|
|
lpm = Deploy.positionManager(
|
|
address(poolManager), address(permit2), 100_000, address(proxyAsImplementation), address(_WETH9), hex"03"
|
|
);
|
|
}
|
|
|
|
function deployWETH() internal returns (IWETH9) {
|
|
address wethAddr = makeAddr("WETH");
|
|
vm.etch(wethAddr, address(wethImpl).code);
|
|
return IWETH9(wethAddr);
|
|
}
|
|
|
|
function deployDescriptor(IPoolManager poolManager, bytes32 label) internal returns (IPositionDescriptor) {
|
|
positionDescriptor = Deploy.positionDescriptor(address(poolManager), address(_WETH9), label, hex"00");
|
|
proxy = Deploy.transparentUpgradeableProxy(address(positionDescriptor), governance, "", hex"03");
|
|
return IPositionDescriptor(address(proxy));
|
|
}
|
|
|
|
function seedBalance(address to) internal {
|
|
IERC20(Currency.unwrap(currency0)).transfer(to, STARTING_USER_BALANCE);
|
|
IERC20(Currency.unwrap(currency1)).transfer(to, STARTING_USER_BALANCE);
|
|
}
|
|
|
|
function approvePosm() internal {
|
|
approvePosmCurrency(currency0);
|
|
approvePosmCurrency(currency1);
|
|
}
|
|
|
|
function approvePosmCurrency(Currency currency) internal {
|
|
// Because POSM uses permit2, we must execute 2 permits/approvals.
|
|
// 1. First, the caller must approve permit2 on the token.
|
|
IERC20(Currency.unwrap(currency)).approve(address(permit2), type(uint256).max);
|
|
// 2. Then, the caller must approve POSM as a spender of permit2. TODO: This could also be a signature.
|
|
permit2.approve(Currency.unwrap(currency), address(lpm), type(uint160).max, type(uint48).max);
|
|
}
|
|
|
|
// Does the same approvals as approvePosm, but for a specific address.
|
|
function approvePosmFor(address addr) internal {
|
|
vm.startPrank(addr);
|
|
approvePosm();
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function seedWeth(address to) internal {
|
|
vm.deal(address(this), STARTING_USER_BALANCE);
|
|
_WETH9.deposit{value: STARTING_USER_BALANCE}();
|
|
_WETH9.transfer(to, STARTING_USER_BALANCE);
|
|
}
|
|
|
|
function seedToken(MockERC20 token, address to) internal {
|
|
token.mint(to, STARTING_USER_BALANCE);
|
|
}
|
|
|
|
function initPoolUnsorted(Currency currencyA, Currency currencyB, IHooks hooks, uint24 fee, uint160 sqrtPriceX96)
|
|
internal
|
|
returns (PoolKey memory poolKey)
|
|
{
|
|
(Currency _currency0, Currency _currency1) =
|
|
SortTokens.sort(MockERC20(Currency.unwrap(currencyA)), MockERC20(Currency.unwrap(currencyB)));
|
|
|
|
(poolKey,) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96);
|
|
}
|
|
|
|
function permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal {
|
|
bytes32 digest = getDigest(operator, tokenId, 1, block.timestamp + 1);
|
|
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
|
|
bytes memory signature = abi.encodePacked(r, s, v);
|
|
|
|
vm.prank(operator);
|
|
lpm.permit(operator, tokenId, block.timestamp + 1, nonce, signature);
|
|
}
|
|
|
|
function getDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline)
|
|
internal
|
|
view
|
|
returns (bytes32 digest)
|
|
{
|
|
digest = keccak256(
|
|
abi.encodePacked(
|
|
"\x19\x01",
|
|
lpm.DOMAIN_SEPARATOR(),
|
|
keccak256(abi.encode(ERC721PermitHash.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline))
|
|
)
|
|
);
|
|
}
|
|
|
|
function getLastDelta() internal view returns (BalanceDelta delta) {
|
|
delta = hook.deltas(hook.numberDeltasReturned() - 1); // just want the most recently written delta
|
|
}
|
|
|
|
function getNetDelta() internal view returns (BalanceDelta delta) {
|
|
uint256 numDeltas = hook.numberDeltasReturned();
|
|
for (uint256 i = 0; i < numDeltas; i++) {
|
|
delta = delta + hook.deltas(i);
|
|
}
|
|
}
|
|
}
|