From e2d26934059a6bcae88eaf8faa6748b738e24dd2 Mon Sep 17 00:00:00 2001 From: sm-stack Date: Wed, 3 Jan 2024 17:54:22 +0900 Subject: [PATCH 1/6] Add basic contracts and test codes --- foundry.toml | 6 + src/BaseFactory.sol | 39 + src/Counter.sol | 14 - src/DiamondHookFutures.sol | 1206 ++++++++++++++++++++++++++++ src/libraries/LiquidityAmounts.sol | 184 +++++ test/Counter.t.sol | 24 - test/DiamondHook.t.sol | 436 ++++++++++ test/utils/DiamondHookImpl.sol | 16 + 8 files changed, 1887 insertions(+), 38 deletions(-) create mode 100644 src/BaseFactory.sol delete mode 100644 src/Counter.sol create mode 100644 src/DiamondHookFutures.sol create mode 100644 src/libraries/LiquidityAmounts.sol delete mode 100644 test/Counter.t.sol create mode 100644 test/DiamondHook.t.sol create mode 100644 test/utils/DiamondHookImpl.sol diff --git a/foundry.toml b/foundry.toml index 25b918f..b26e6c5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,11 @@ src = "src" out = "out" libs = ["lib"] +solc_version = '0.8.20' +evm_version = "paris" + +[profile.ci] +fuzz_runs = 100000 +solc = "./lib/v4-core/bin/solc-static-linux" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/src/BaseFactory.sol b/src/BaseFactory.sol new file mode 100644 index 0000000..5b64a95 --- /dev/null +++ b/src/BaseFactory.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; + +abstract contract BaseFactory { + /// @notice zero out all but the first byte of the address which is all 1's + uint160 public constant UNISWAP_FLAG_MASK = 0xff << 152; + + // Uniswap hook contracts must have specific flags encoded in the first byte of their address + address public immutable TargetPrefix; + + constructor(address _targetPrefix) { + TargetPrefix = _targetPrefix; + } + + function deploy(IPoolManager poolManager, bytes32 salt) public virtual returns (address); + + function mineDeploy(IPoolManager poolManager) external returns (address) { + return deploy(poolManager,0); + } + + function _computeHookAddress(IPoolManager poolManager, bytes32 salt) internal view returns (address) { + bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, _hashBytecode(poolManager))); + return address(uint160(uint256(hash))); + } + + /// @dev The implementing contract must override this function to return the bytecode hash of its contract + /// For example, the CounterHook contract would return: + /// bytecodeHash = keccak256(abi.encodePacked(type(CounterHook).creationCode, abi.encode(poolManager))); + function _hashBytecode(IPoolManager poolManager) internal pure virtual returns (bytes32 bytecodeHash); + + function _isPrefix(address _address) internal view returns (bool) { + // zero out all but the first byte of the address + address actualPrefix = address(uint160(_address) & UNISWAP_FLAG_MASK); + return actualPrefix == TargetPrefix; + } +} \ No newline at end of file diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/DiamondHookFutures.sol b/src/DiamondHookFutures.sol new file mode 100644 index 0000000..e3efa92 --- /dev/null +++ b/src/DiamondHookFutures.sol @@ -0,0 +1,1206 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {LiquidityAmounts} from "./libraries/LiquidityAmounts.sol"; +import {BaseHook} from "@uniswap/v4-periphery/contracts/BaseHook.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {BaseFactory} from "./BaseFactory.sol"; + +contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + using BalanceDeltaLibrary for BalanceDelta; + using FeeLibrary for uint24; + using TickMath for int24; + using Pool for Pool.State; + using SafeERC20 for ERC20; + using SafeERC20 for PoolManager; + + error AlreadyInitialized(); + error NotPoolManagerToken(); + error InvalidTickSpacing(); + error InvalidMsgValue(); + error OnlyModifyViaHook(); + error PoolAlreadyOpened(); + error PoolNotOpen(); + error ArbTooSmall(); + error LiquidityZero(); + error InsufficientHedgeCommitted(); + error MintZero(); + error BurnZero(); + error BurnExceedsSupply(); + error WithdrawExceedsAvailable(); + error OnlyCommitter(); + error PriceOutOfBounds(); + error TotalSupplyZero(); + error InvalidCurrencyDelta(); + + uint24 internal constant _PIPS = 1000000; + + int24 public immutable lowerTick; + int24 public immutable upperTick; + int24 public immutable tickSpacing; + uint24 public immutable baseBeta; // % expressed as uint < 1e6 + uint24 public immutable decayRate; // % expressed as uint < 1e6 + uint24 public immutable vaultRedepositRate; // % expressed as uint < 1e6 + + /// @dev these could be TRANSIENT STORAGE eventually + uint256 internal _a0; + uint256 internal _a1; + /// ---------- + + uint256 public lastBlockOpened; + uint256 public lastBlockReset; + uint256 public hedgeRequired0; + uint256 public hedgeRequired1; + uint256 public hedgeCommitted0; + uint256 public hedgeCommitted1; + uint160 public committedSqrtPriceX96; + PoolKey public poolKey; + address public committer; + bool public initialized; + + struct PoolManagerCalldata { + uint256 amount; /// mintAmount | burnAmount | newSqrtPriceX96 (inferred from actionType) + address msgSender; + address receiver; + uint8 actionType; /// 0 = mint | 1 = burn | 2 = arbSwap + } + + struct ArbSwapParams { + uint160 sqrtPriceX96; + uint160 newSqrtPriceX96; + uint160 sqrtPriceX96Lower; + uint160 sqrtPriceX96Upper; + uint128 liquidity; + uint24 betaFactor; + } + + constructor( + IPoolManager _poolManager, + int24 _tickSpacing, + uint24 _baseBeta, + uint24 _decayRate, + uint24 _vaultRedepositRate + ) BaseHook(_poolManager) ERC20("Diamond LP Token", "DLPT") { + lowerTick = _tickSpacing.minUsableTick(); + upperTick = _tickSpacing.maxUsableTick(); + tickSpacing = _tickSpacing; + require( + _baseBeta < _PIPS && + _decayRate <= _baseBeta && + _vaultRedepositRate < _PIPS + ); + baseBeta = _baseBeta; + decayRate = _decayRate; + vaultRedepositRate = _vaultRedepositRate; + } + + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) external view returns (bytes4) { + if (msg.sender != address(poolManager)) revert NotPoolManagerToken(); + return + bytes4( + keccak256( + "onERC1155Received(address,address,uint256,uint256,bytes)" + ) + ); + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external view returns (bytes4) { + if (msg.sender != address(poolManager)) revert NotPoolManagerToken(); + return + bytes4( + keccak256( + "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)" + ) + ); + } + + function supportsInterface(bytes4) external pure returns (bool) { + return true; + } + + function getHooksCalls() public pure override returns (Hooks.Calls memory) { + return + Hooks.Calls({ + beforeInitialize: true, + afterInitialize: false, + beforeModifyPosition: true, + afterModifyPosition: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false + }); + } + + function beforeInitialize( + address, + PoolKey calldata poolKey_, + uint160 sqrtPriceX96 + ) external override returns (bytes4) { + /// can only initialize one pool once. + + if (initialized) revert AlreadyInitialized(); + + /// validate tick bounds on pool initialization + if (poolKey_.tickSpacing != tickSpacing) revert InvalidTickSpacing(); + + /// initialize state variable + poolKey = poolKey_; + lastBlockOpened = block.number - 1; + lastBlockReset = block.number; + committedSqrtPriceX96 = sqrtPriceX96; + initialized = true; + + return this.beforeInitialize.selector; + } + + function beforeSwap( + address sender, + PoolKey calldata, + IPoolManager.SwapParams calldata + ) external view override returns (bytes4) { + /// if swap is coming from the hook then its a 1 wei swap to kick the price and not a "normal" swap + if (sender != address(this)) { + /// disallow normal swaps at top of block + if (lastBlockOpened != block.number) revert PoolNotOpen(); + } + return BaseHook.beforeSwap.selector; + } + + function afterSwap( + address sender, + PoolKey calldata, + IPoolManager.SwapParams calldata, + BalanceDelta + ) external override returns (bytes4) { + /// if swap is coming from the hook then its a 1 wei swap to kick the price and not a "normal" swap + if (sender != address(this)) { + /// cannot move price to edge of LP positin + PoolId poolId = PoolIdLibrary.toId(poolKey); + (uint160 sqrtPriceX96, , , , , ) = poolManager.getSlot0(poolId); + uint160 sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(lowerTick); + uint160 sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(upperTick); + if ( + sqrtPriceX96 >= sqrtPriceX96Upper || + sqrtPriceX96 <= sqrtPriceX96Lower + ) revert PriceOutOfBounds(); + + Position.Info memory info = PoolManager( + payable(address(poolManager)) + ).getPosition(poolId, address(this), lowerTick, upperTick); + + (uint256 current0, uint256 current1) = LiquidityAmounts + .getAmountsForLiquidity( + sqrtPriceX96, + sqrtPriceX96Lower, + sqrtPriceX96Upper, + info.liquidity + ); + + (uint256 need0, uint256 need1) = LiquidityAmounts + .getAmountsForLiquidity( + committedSqrtPriceX96, + sqrtPriceX96Lower, + sqrtPriceX96Upper, + info.liquidity + ); + + if (need0 > current0) { + uint256 min0 = need0 - current0; + if (min0 > hedgeCommitted0) revert InsufficientHedgeCommitted(); + hedgeRequired0 = min0; + hedgeRequired1 = 0; + } else if (need1 > current1) { + uint256 min1 = need1 - current1; + if (min1 > hedgeCommitted1) revert InsufficientHedgeCommitted(); + hedgeRequired1 = min1; + hedgeRequired0 = 0; + } else { + hedgeRequired0 = 0; + hedgeRequired1 = 0; + } + } + + return BaseHook.afterSwap.selector; + } + + function beforeModifyPosition( + address sender, + PoolKey calldata, + IPoolManager.ModifyPositionParams calldata + ) external view override returns (bytes4) { + /// force LPs to provide liquidity through hook + if (sender != address(this)) revert OnlyModifyViaHook(); + return BaseHook.beforeModifyPosition.selector; + } + + /// method called back on PoolManager.lock() + function lockAcquired( + bytes calldata data_ + ) external override poolManagerOnly returns (bytes memory) { + /// decode calldata passed through lock() + + PoolManagerCalldata memory pmCalldata = abi.decode( + data_, + (PoolManagerCalldata) + ); + /// first case mint action + if (pmCalldata.actionType == 0) _lockAcquiredMint(pmCalldata); + /// second case burn action + if (pmCalldata.actionType == 1) _lockAcquiredBurn(pmCalldata); + /// third case arbSwap action + if (pmCalldata.actionType == 2) _lockAcquiredArb(pmCalldata); + } + + /// anyone can call this method to "open the pool" with top of block arb swap. + /// no swaps will be processed in a block unless this method is called first in that block. + function openPool(uint160 newSqrtPriceX96_) external payable nonReentrant { + if(totalSupply() == 0) revert TotalSupplyZero(); + + /// encode calldata to pass through lock() + bytes memory data = abi.encode( + PoolManagerCalldata({ + amount: uint256(newSqrtPriceX96_), + msgSender: msg.sender, + receiver: msg.sender, + actionType: 2 /// arbSwap action + }) + ); + + /// begin pool actions (passing data through lock() into _lockAcquiredArb()) + poolManager.lock(data); + + committer = msg.sender; + committedSqrtPriceX96 = newSqrtPriceX96_; + lastBlockOpened = block.number; + + /// handle eth refunds (question: is this necessary?) + if (poolKey.currency0.isNative()) { + uint256 leftover = address(this).balance; + if (leftover > 0) _nativeTransfer(msg.sender, leftover); + } + if (poolKey.currency1.isNative()) { + uint256 leftover = address(this).balance; + if (leftover > 0) _nativeTransfer(msg.sender, leftover); + } + } + + function depositHedgeCommitment( + uint256 amount0, + uint256 amount1 + ) external payable { + if (lastBlockOpened != block.number) revert PoolNotOpen(); + + if (amount0 > 0) { + if (poolKey.currency0.isNative()) { + if (msg.value != amount0) revert InvalidMsgValue(); + } else { + ERC20(Currency.unwrap(poolKey.currency0)).safeTransferFrom( + msg.sender, + address(this), + amount0 + ); + } + hedgeCommitted0 += amount0; + } + + if (amount1 > 0) { + if (poolKey.currency1.isNative()) { + if (msg.value != amount1) revert InvalidMsgValue(); + } else { + ERC20(Currency.unwrap(poolKey.currency1)).safeTransferFrom( + msg.sender, + address(this), + amount1 + ); + } + hedgeCommitted1 += amount1; + } + } + + function withdrawHedgeCommitment( + uint256 amount0, + uint256 amount1 + ) external nonReentrant { + if (committer != msg.sender) revert OnlyCommitter(); + + if (amount0 > 0) { + uint256 withdrawAvailable0 = hedgeRequired0 > 0 + ? hedgeCommitted0 - hedgeRequired0 + : hedgeCommitted0; + if (amount0 > withdrawAvailable0) revert WithdrawExceedsAvailable(); + hedgeCommitted0 -= amount0; + if (poolKey.currency0.isNative()) { + _nativeTransfer(msg.sender, amount0); + } else { + ERC20(Currency.unwrap(poolKey.currency0)).safeTransfer( + msg.sender, + amount0 + ); + } + } + + if (amount1 > 0) { + uint256 withdrawAvailable1 = hedgeRequired1 > 0 + ? hedgeCommitted1 - hedgeRequired1 + : hedgeCommitted1; + if (amount1 > withdrawAvailable1) revert WithdrawExceedsAvailable(); + hedgeCommitted1 -= amount1; + if (poolKey.currency1.isNative()) { + _nativeTransfer(msg.sender, amount1); + } else { + ERC20(Currency.unwrap(poolKey.currency1)).safeTransfer( + msg.sender, + amount1 + ); + } + } + } + + /// how LPs add and remove liquidity into the hook + function mint( + uint256 mintAmount_, + address receiver_ + ) external payable nonReentrant returns (uint256 amount0, uint256 amount1) { + if (mintAmount_ == 0) revert MintZero(); + + /// encode calldata to pass through lock() + bytes memory data = abi.encode( + PoolManagerCalldata({ + amount: mintAmount_, + msgSender: msg.sender, + receiver: receiver_, + actionType: 0 /// mint action + }) + ); + /// state variables to be able to bubble up amount0 and amount1 as return args + _a0 = _a1 = 0; + + /// begin pool actions (passing data through lock() into _lockAcquiredMint()) + poolManager.lock(data); + + /// handle eth refunds + if (poolKey.currency0.isNative()) { + uint256 leftover = address(this).balance - hedgeCommitted0; + if (leftover > 0) _nativeTransfer(msg.sender, leftover); + } + if (poolKey.currency1.isNative()) { + uint256 leftover = address(this).balance - hedgeCommitted1; + if (leftover > 0) _nativeTransfer(msg.sender, leftover); + } + + /// set return arguments (stored during lock callback) + amount0 = _a0; + amount1 = _a1; + + /// remit ERC20 liquidity shares to target receiver + _mint(receiver_, mintAmount_); + } + + function burn( + uint256 burnAmount_, + address receiver_ + ) external nonReentrant returns (uint256 amount0, uint256 amount1) { + if (burnAmount_ == 0) revert BurnZero(); + if (totalSupply() < burnAmount_) revert BurnExceedsSupply(); + + /// encode calldata to pass through lock() + bytes memory data = abi.encode( + PoolManagerCalldata({ + amount: burnAmount_, + msgSender: msg.sender, + receiver: receiver_, + actionType: 1 // burn action + }) + ); + + /// state variables to be able to bubble up amount0 and amount1 as return args + _a0 = _a1 = 0; + + /// begin pool actions (passing data through lock() into _lockAcquiredBurn()) + poolManager.lock(data); + + /// set return arguments (stored during lock callback) + amount0 = _a0; + amount1 = _a1; + + /// burn ERC20 LP shares of the caller + _burn(msg.sender, burnAmount_); + } + + function _lockAcquiredArb(PoolManagerCalldata memory pmCalldata) internal { + uint256 blockDelta = _checkLastOpen(); + + ( + uint160 sqrtPriceX96Real, + uint160 sqrtPriceX96Virtual, + uint128 liquidityReal, + uint128 liquidityVirtual + ) = _resetLiquidity(false); + + uint160 newSqrtPriceX96 = SafeCast.toUint160(pmCalldata.amount); + + /// compute swap amounts, swap direction, and amount of liquidity to mint + uint160 sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(lowerTick); + uint160 sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(upperTick); + { + (uint256 swap0, uint256 swap1) = _getArbSwap( + ArbSwapParams({ + sqrtPriceX96: sqrtPriceX96Virtual, + newSqrtPriceX96: newSqrtPriceX96, + sqrtPriceX96Lower: sqrtPriceX96Lower, + sqrtPriceX96Upper: sqrtPriceX96Upper, + liquidity: liquidityVirtual, + betaFactor: _getBeta(blockDelta) + }) + ); + + /// burn all liquidity + if (liquidityReal > 0) { + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: -SafeCast.toInt256( + uint256(liquidityReal) + ), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + _clear1155Balances(); + } + + /// swap 1 wei in zero liquidity to kick the price to newSqrtPriceX96 + bool zeroForOne = newSqrtPriceX96 < sqrtPriceX96Virtual; + if (newSqrtPriceX96 != sqrtPriceX96Real) { + poolManager.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: newSqrtPriceX96 < sqrtPriceX96Real, + amountSpecified: 1, + sqrtPriceLimitX96: newSqrtPriceX96 + }) + ); + } + + /// handle swap transfers (send to / transferFrom arber) + if (zeroForOne) { + /// transfer swapInAmt to PoolManager + _transferFromOrTransferNative( + poolKey.currency0, + pmCalldata.msgSender, + address(poolManager), + swap0 + ); + poolManager.settle(poolKey.currency0); + + /// transfer swapOutAmt to arber + poolManager.take(poolKey.currency1, pmCalldata.receiver, swap1); + } else { + /// transfer swapInAmt to PoolManager + + _transferFromOrTransferNative( + poolKey.currency1, + pmCalldata.msgSender, + address(poolManager), + swap1 + ); + + poolManager.settle(poolKey.currency1); + /// transfer swapOutAmt to arber + poolManager.take(poolKey.currency0, pmCalldata.receiver, swap0); + } + } + + ( + uint256 totalHoldings0, + uint256 totalHoldings1 + ) = _checkCurrencyBalances(); + + uint128 newLiquidity = LiquidityAmounts.getLiquidityForAmounts( + newSqrtPriceX96, + sqrtPriceX96Lower, + sqrtPriceX96Upper, + totalHoldings0, + totalHoldings1 + ); + + /// mint new liquidity around newSqrtPriceX96 + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: SafeCast.toInt256(uint256(newLiquidity)), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + /// if any positive balances remain in PoolManager after all operations, mint erc1155 shares + _mintLeftover(); + } + + function _lockAcquiredMint(PoolManagerCalldata memory pmCalldata) internal { + uint256 totalSupply = totalSupply(); + + if (totalSupply == 0) { + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: SafeCast.toInt256(pmCalldata.amount), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + // casting to uint256 is ok for minting + _a0 = SafeCast.toUint256( + poolManager.currencyDelta(address(this), poolKey.currency0) + ); + _a1 = SafeCast.toUint256( + poolManager.currencyDelta(address(this), poolKey.currency1) + ); + if (_a0 > 0) { + _transferFromOrTransferNative( + poolKey.currency0, + pmCalldata.msgSender, + address(poolManager), + _a0 + ); + poolManager.settle(poolKey.currency0); + } + if (_a1 > 0) { + _transferFromOrTransferNative( + poolKey.currency1, + pmCalldata.msgSender, + address(poolManager), + _a1 + ); + poolManager.settle(poolKey.currency1); + } + } else { + uint128 liquidity; + /// if this is first touch in this block, then we need to _resetLiquidity() first + if (lastBlockOpened != block.number) { + (, , liquidity, ) = _resetLiquidity(true); + } else { + Position.Info memory info = PoolManager( + payable(address(poolManager)) + ).getPosition( + PoolIdLibrary.toId(poolKey), + address(this), + lowerTick, + upperTick + ); + liquidity = info.liquidity; + } + + if (liquidity > 0) + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: -SafeCast.toInt256(uint256(liquidity)), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + _checkCurrencyBalances(); + + (, uint256 leftOver0, , uint256 leftOver1) = _get1155Balances(); + + // mint back the position. + uint256 newLiquidity = liquidity + + FullMath.mulDiv(pmCalldata.amount, liquidity, totalSupply); + + if (newLiquidity > 0) + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: SafeCast.toInt256(newLiquidity), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + uint256 amount0 = SafeCast.toUint256( + poolManager.currencyDelta(address(this), poolKey.currency0) + ); + uint256 amount1 = SafeCast.toUint256( + poolManager.currencyDelta(address(this), poolKey.currency1) + ); + + amount0 += FullMath.mulDivRoundingUp( + leftOver0, + pmCalldata.amount, + totalSupply + ); + amount1 += FullMath.mulDivRoundingUp( + leftOver1, + pmCalldata.amount, + totalSupply + ); + + if (amount0 > 0) { + _transferFromOrTransferNative( + poolKey.currency0, + pmCalldata.msgSender, + address(poolManager), + amount0 + ); + poolManager.settle(poolKey.currency0); + } + + if (amount1 > 0) { + _transferFromOrTransferNative( + poolKey.currency1, + pmCalldata.msgSender, + address(poolManager), + amount1 + ); + poolManager.settle(poolKey.currency1); + } + + _a0 = amount0; + _a1 = amount1; + } + _mintLeftover(); + + if (hedgeRequired0 > 0) { + hedgeRequired0 += FullMath.mulDiv( + hedgeRequired0, + pmCalldata.amount, + totalSupply + ); + } + if (hedgeRequired1 > 0) { + hedgeRequired1 += FullMath.mulDiv( + hedgeRequired1, + pmCalldata.amount, + totalSupply + ); + } + + if ( + hedgeRequired0 > hedgeCommitted0 || hedgeRequired1 > hedgeCommitted1 + ) revert InsufficientHedgeCommitted(); + } + + // this function gets the supply of LP tokens, the supply of LP tokens to removes, + // the total amount of tokens owned by the poolManager (liquidity + vault), + // and takes remove token amt/total token amt from all controlled tokens. + function _lockAcquiredBurn(PoolManagerCalldata memory pmCalldata) internal { + /// burn everything, positions and erc1155 + uint256 totalSupply = totalSupply(); + + uint128 liquidity; + /// if this is first touch in this block, then we need to _resetLiquidity() first + if (lastBlockOpened != block.number) { + (, , liquidity, ) = _resetLiquidity(true); + } else { + Position.Info memory info = PoolManager( + payable(address(poolManager)) + ).getPosition( + PoolIdLibrary.toId(poolKey), + address(this), + lowerTick, + upperTick + ); + liquidity = info.liquidity; + } + + if (liquidity > 0) + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: -SafeCast.toInt256(uint256(liquidity)), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + _clear1155Balances(); + + ( + uint256 currency0Balance, + uint256 currency1Balance + ) = _checkCurrencyBalances(); + uint256 amount0 = FullMath.mulDiv( + pmCalldata.amount, + currency0Balance, + totalSupply + ); + uint256 amount1 = FullMath.mulDiv( + pmCalldata.amount, + currency1Balance, + totalSupply + ); + + uint256 newLiquidity = liquidity - + FullMath.mulDiv(pmCalldata.amount, liquidity, totalSupply); + + if (newLiquidity > 0) + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: SafeCast.toInt256(newLiquidity), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + ( + currency0Balance, + currency1Balance + ) = _checkCurrencyBalances(); + + amount0 = amount0 > currency0Balance ? currency0Balance : amount0; + amount1 = amount1 > currency1Balance ? currency1Balance : amount1; + + // take amounts and send them to receiver + if (amount0 > 0) { + poolManager.take(poolKey.currency0, pmCalldata.receiver, amount0); + } + if (amount1 > 0) { + poolManager.take(poolKey.currency1, pmCalldata.receiver, amount1); + } + + _a0 = amount0; + _a1 = amount1; + _mintLeftover(); + if (hedgeRequired0 > 0) { + hedgeRequired0 -= FullMath.mulDiv( + hedgeRequired0, + pmCalldata.amount, + totalSupply + ); + } + if (hedgeRequired1 > 0) { + hedgeRequired1 -= FullMath.mulDiv( + hedgeRequired1, + pmCalldata.amount, + totalSupply + ); + } + } + + function min(uint256 a, uint256 b) public pure returns (uint256) { + return a < b ? a : b; + } + + function max(uint256 a, uint256 b) public pure returns (uint256) { + return a > b ? a : b; + } + + function _resetLiquidity( + bool isMintOrBurn + ) + internal + returns ( + uint160 sqrtPriceX96, + uint160 newSqrtPriceX96, + uint128 liquidity, + uint128 newLiquidity + ) + { + (sqrtPriceX96, , , , , ) = poolManager.getSlot0( + PoolIdLibrary.toId(poolKey) + ); + + Position.Info memory info = PoolManager(payable(address(poolManager))) + .getPosition( + PoolIdLibrary.toId(poolKey), + address(this), + lowerTick, + upperTick + ); + if (lastBlockReset <= lastBlockOpened) { + if (info.liquidity > 0) + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: -SafeCast.toInt256( + uint256(info.liquidity) + ), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + _clear1155Balances(); + + (newSqrtPriceX96, newLiquidity) = _getResetPriceAndLiquidity( + committedSqrtPriceX96, + isMintOrBurn + ); + + if (isMintOrBurn) { + /// swap 1 wei in zero liquidity to kick the price to committedSqrtPriceX96 + if (sqrtPriceX96 != newSqrtPriceX96) { + poolManager.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: newSqrtPriceX96 < sqrtPriceX96, + amountSpecified: 1, + sqrtPriceLimitX96: newSqrtPriceX96 + }) + ); + } + + if (newLiquidity > 0) + poolManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams({ + liquidityDelta: SafeCast.toInt256( + uint256(newLiquidity) + ), + tickLower: lowerTick, + tickUpper: upperTick + }) + ); + + liquidity = newLiquidity; + + if (hedgeCommitted0 > 0) { + poolKey.currency0.transfer( + address(poolManager), + hedgeCommitted0 + ); + poolManager.settle(poolKey.currency0); + } + if (hedgeCommitted1 > 0) { + poolKey.currency1.transfer( + address(poolManager), + hedgeCommitted1 + ); + poolManager.settle(poolKey.currency1); + } + + _mintLeftover(); + } else { + if (hedgeCommitted0 > 0) { + poolKey.currency0.transfer( + address(poolManager), + hedgeCommitted0 + ); + poolManager.settle(poolKey.currency0); + } + if (hedgeCommitted1 > 0) { + poolKey.currency1.transfer( + address(poolManager), + hedgeCommitted1 + ); + poolManager.settle(poolKey.currency1); + } + } + + // reset hedger variables + hedgeRequired0 = 0; + hedgeRequired1 = 0; + hedgeCommitted0 = 0; + hedgeCommitted1 = 0; + + // store reset + lastBlockReset = block.number; + } else { + liquidity = info.liquidity; + newLiquidity = info.liquidity; + newSqrtPriceX96 = sqrtPriceX96; + } + } + + function _mintLeftover() internal { + ( + uint256 currencyBalance0, + uint256 currencyBalance1 + ) = _checkCurrencyBalances(); + + if (currencyBalance0 > 0) { + poolManager.mint( + poolKey.currency0, + address(this), + currencyBalance0 + ); + } + if (currencyBalance1 > 0) { + poolManager.mint( + poolKey.currency1, + address(this), + currencyBalance1 + ); + } + } + + function _clear1155Balances() internal { + ( + uint256 currency0Id, + uint256 leftOver0, + uint256 currency1Id, + uint256 leftOver1 + ) = _get1155Balances(); + + if (leftOver0 > 0) + PoolManager(payable(address(poolManager))).safeTransferFrom( + address(this), + address(poolManager), + currency0Id, + leftOver0, + "" + ); + + if (leftOver1 > 0) + PoolManager(payable(address(poolManager))).safeTransferFrom( + address(this), + address(poolManager), + currency1Id, + leftOver1, + "" + ); + } + + function _get1155Balances() + internal + view + returns ( + uint256 currency0Id, + uint256 leftOver0, + uint256 currency1Id, + uint256 leftOver1 + ) + { + currency0Id = CurrencyLibrary.toId(poolKey.currency0); + leftOver0 = poolManager.balanceOf(address(this), currency0Id); + + currency1Id = CurrencyLibrary.toId(poolKey.currency1); + leftOver1 = poolManager.balanceOf(address(this), currency1Id); + } + + function _transferFromOrTransferNative( + Currency currency, + address sender, + address target, + uint256 amount + ) internal { + if (currency.isNative()) { + _nativeTransfer(target, amount); + } else { + ERC20(Currency.unwrap(currency)).safeTransferFrom( + sender, + target, + amount + ); + } + } + + function _nativeTransfer(address to, uint256 amount) internal { + bool success; + assembly { + // Transfer the ETH and store if it succeeded or not. + success := call(gas(), to, amount, 0, 0, 0, 0) + } + + if (!success) revert CurrencyLibrary.NativeTransferFailed(); + } + + function _checkCurrencyBalances() internal view returns (uint256, uint256) { + int256 currency0BalanceRaw = poolManager.currencyDelta( + address(this), + poolKey.currency0 + ); + if (currency0BalanceRaw > 0) revert InvalidCurrencyDelta(); + uint256 currency0Balance = SafeCast.toUint256(-currency0BalanceRaw); + int256 currency1BalanceRaw = poolManager.currencyDelta( + address(this), + poolKey.currency1 + ); + if (currency1BalanceRaw > 0) revert InvalidCurrencyDelta(); + uint256 currency1Balance = SafeCast.toUint256(-currency1BalanceRaw); + + return (currency0Balance, currency1Balance); + } + + function _getResetPriceAndLiquidity( + uint160 lastCommittedSqrtPriceX96, + bool isMintOrBurn + ) internal view returns (uint160, uint128) { + ( + uint256 totalHoldings0, + uint256 totalHoldings1 + ) = _checkCurrencyBalances(); + + uint160 sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(lowerTick); + uint160 sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(upperTick); + + uint160 finalSqrtPriceX96; + { + (uint256 maxLiquidity0, uint256 maxLiquidity1) = LiquidityAmounts + .getAmountsForLiquidity( + lastCommittedSqrtPriceX96, + sqrtPriceX96Lower, + sqrtPriceX96Upper, + LiquidityAmounts.getLiquidityForAmounts( + lastCommittedSqrtPriceX96, + sqrtPriceX96Lower, + sqrtPriceX96Upper, + totalHoldings0, + totalHoldings1 + ) + ); + + /// NOTE one of these should be roughly zero but we don't know which one so we just increase both + // (adding 0 or dust to the other side should cause no issue or major imprecision) + uint256 extra0 = FullMath.mulDiv( + totalHoldings0 - maxLiquidity0, + vaultRedepositRate, + _PIPS + ); + uint256 extra1 = FullMath.mulDiv( + totalHoldings1 - maxLiquidity1, + vaultRedepositRate, + _PIPS + ); + + /// NOTE this algorithm only works if liquidity position is full range + uint256 priceX96 = FullMath.mulDiv( + maxLiquidity1 + extra1, + 1 << 96, + maxLiquidity0 + extra0 + ); + finalSqrtPriceX96 = SafeCast.toUint160(_sqrt(priceX96) * (1 << 48)); + } + + if ( + finalSqrtPriceX96 >= sqrtPriceX96Upper || + finalSqrtPriceX96 <= sqrtPriceX96Lower + ) revert PriceOutOfBounds(); + + if (isMintOrBurn) { + totalHoldings0 -= 1; + totalHoldings1 -= 1; + } + uint128 finalLiquidity = LiquidityAmounts.getLiquidityForAmounts( + finalSqrtPriceX96, + sqrtPriceX96Lower, + sqrtPriceX96Upper, + totalHoldings0, + totalHoldings1 + ); + + return (finalSqrtPriceX96, finalLiquidity); + } + + function _getArbSwap( + ArbSwapParams memory params + ) internal pure returns (uint256 swap0, uint256 swap1) { + /// cannot do arb in zero liquidity + if (params.liquidity == 0) revert LiquidityZero(); + + /// cannot move price to edge of LP positin + if ( + params.newSqrtPriceX96 >= params.sqrtPriceX96Upper || + params.newSqrtPriceX96 <= params.sqrtPriceX96Lower + ) revert PriceOutOfBounds(); + + /// get amount0/1 of current liquidity + (uint256 current0, uint256 current1) = LiquidityAmounts + .getAmountsForLiquidity( + params.sqrtPriceX96, + params.sqrtPriceX96Lower, + params.sqrtPriceX96Upper, + params.liquidity + ); + + /// get amount0/1 of current liquidity if price was newSqrtPriceX96 + (uint256 new0, uint256 new1) = LiquidityAmounts.getAmountsForLiquidity( + params.newSqrtPriceX96, + params.sqrtPriceX96Lower, + params.sqrtPriceX96Upper, + params.liquidity + ); + + + // question: Is this error necessary? + if (new0 == current0 || new1 == current1) revert ArbTooSmall(); + bool zeroForOne = new0 > current0; + + /// differential of info.liquidity amount0/1 at those two prices gives X and Y of classic UniV2 swap + /// to get (1-Beta)*X and (1-Beta)*Y for our swap apply `factor` + swap0 = FullMath.mulDiv( + zeroForOne ? new0 - current0 : current0 - new0, + params.betaFactor, + _PIPS + ); + swap1 = FullMath.mulDiv( + zeroForOne ? current1 - new1 : new1 - current1, + params.betaFactor, + _PIPS + ); + } + + function _getBeta(uint256 blockDelta) internal view returns (uint24) { + /// if blockDelta = 1 then decay is 0; if blockDelta = 2 then decay is decayRate; if blockDelta = 3 then decay is 2*decayRate etc. + uint256 decayAmt = (blockDelta - 1) * decayRate; + /// decayAmt downcast is safe here because we know baseBeta < 10000 + uint24 subtractAmt = decayAmt >= baseBeta + ? 0 + : baseBeta - uint24(decayAmt); + + return _PIPS - subtractAmt; + } + + function _checkLastOpen() internal view returns (uint256) { + /// compute block delta since last time pool was utilized. + uint256 blockDelta = block.number - lastBlockOpened; + + /// revert if block delta is 0 (pool is already open, top of block arb already happened) + if (blockDelta == 0) revert PoolAlreadyOpened(); + + return blockDelta; + } + + function computeDecPriceFromNewSQRTPrice( + uint160 price + ) internal pure returns (uint256 y) { + y = FullMath.mulDiv(uint256(price) ** 2, 1, 2 ** 192); + } + + function _sqrt(uint256 x) internal pure returns (uint256 y) { + uint256 z = (x + 1) / 2; + y = x; + while (z < y) { + y = z; + z = (x / z + z) / 2; + } + } +} \ No newline at end of file diff --git a/src/libraries/LiquidityAmounts.sol b/src/libraries/LiquidityAmounts.sol new file mode 100644 index 0000000..4664a01 --- /dev/null +++ b/src/libraries/LiquidityAmounts.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.8.0; + +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; + +/// @title Liquidity amount functions +/// @notice Provides functions for computing liquidity amounts from token amounts and prices +library LiquidityAmounts { + function toUint128(uint256 x) private pure returns (uint128 y) { + require((y = uint128(x)) == x); + } + + /// @notice Computes the amount of liquidity received for a given amount of token0 and price range + /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower)). + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param amount0 The amount0 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount0( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount0 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) + (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + uint256 intermediate = FullMath.mulDiv( + sqrtRatioAX96, + sqrtRatioBX96, + FixedPoint96.Q96 + ); + return + toUint128( + FullMath.mulDiv( + amount0, + intermediate, + sqrtRatioBX96 - sqrtRatioAX96 + ) + ); + } + + /// @notice Computes the amount of liquidity received for a given amount of token1 and price range + /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)). + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param amount1 The amount1 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount1( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) + (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + return + toUint128( + FullMath.mulDiv( + amount1, + FixedPoint96.Q96, + sqrtRatioBX96 - sqrtRatioAX96 + ) + ); + } + + /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current + /// pool prices and the prices at the tick boundaries + function getLiquidityForAmounts( + uint160 sqrtRatioX96, + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) + (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + if (sqrtRatioX96 <= sqrtRatioAX96) { + liquidity = getLiquidityForAmount0( + sqrtRatioAX96, + sqrtRatioBX96, + amount0 + ); + } else if (sqrtRatioX96 < sqrtRatioBX96) { + uint128 liquidity0 = getLiquidityForAmount0( + sqrtRatioX96, + sqrtRatioBX96, + amount0 + ); + uint128 liquidity1 = getLiquidityForAmount1( + sqrtRatioAX96, + sqrtRatioX96, + amount1 + ); + + liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; + } else { + liquidity = getLiquidityForAmount1( + sqrtRatioAX96, + sqrtRatioBX96, + amount1 + ); + } + } + + /// @notice Computes the amount of token0 for a given amount of liquidity and a price range + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The liquidity being valued + /// @return amount0 The amount0 + function getAmount0ForLiquidity( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) + (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + FullMath.mulDiv( + uint256(liquidity) << FixedPoint96.RESOLUTION, + sqrtRatioBX96 - sqrtRatioAX96, + sqrtRatioBX96 + ) / sqrtRatioAX96; + } + + /// @notice Computes the amount of token1 for a given amount of liquidity and a price range + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The liquidity being valued + /// @return amount1 The amount1 + function getAmount1ForLiquidity( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) + (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + FullMath.mulDiv( + liquidity, + sqrtRatioBX96 - sqrtRatioAX96, + FixedPoint96.Q96 + ); + } + + /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current + /// pool prices and the prices at the tick boundaries + function getAmountsForLiquidity( + uint160 sqrtRatioX96, + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount0, uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) + (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + if (sqrtRatioX96 <= sqrtRatioAX96) { + amount0 = getAmount0ForLiquidity( + sqrtRatioAX96, + sqrtRatioBX96, + liquidity + ); + } else if (sqrtRatioX96 < sqrtRatioBX96) { + amount0 = getAmount0ForLiquidity( + sqrtRatioX96, + sqrtRatioBX96, + liquidity + ); + amount1 = getAmount1ForLiquidity( + sqrtRatioAX96, + sqrtRatioX96, + liquidity + ); + + } else { + amount1 = getAmount1ForLiquidity( + sqrtRatioAX96, + sqrtRatioBX96, + liquidity + ); + } + } +} \ No newline at end of file diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index e9b9e6a..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console2} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/DiamondHook.t.sol b/test/DiamondHook.t.sol new file mode 100644 index 0000000..197cc7e --- /dev/null +++ b/test/DiamondHook.t.sol @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey, PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {LiquidityAmounts} from "../src/libraries/LiquidityAmounts.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {console} from "forge-std/console.sol"; + +import {DiamondHookPoC} from "../src/DiamondHookFutures.sol"; +import {DiamondHookImpl} from "./utils/DiamondHookImpl.sol"; + +contract TestDiamondHook is Test, Deployers, GasSnapshot { + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + + PoolManager manager; + address hookAddress; + TestERC20 token0; + TestERC20 token1; + PoolId poolId; + DiamondHookPoC hook = DiamondHookPoC(address( + uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG |Hooks.AFTER_SWAP_FLAG + ) + )); + PoolKey poolKey; + + PoolSwapTest swapRouter; + + uint24 constant PIPS = 1000000; + int24 public tickSpacing = 10; + uint24 public baseBeta = PIPS/2; // % expressed as uint < 1e6 + uint24 public decayRate = PIPS/10; // % expressed as uint < 1e6 + uint24 public vaultRedepositRate = PIPS/10; // % expressed as uint < 1e6 + // we also want to pass in a minimum constant amount (maybe even a % of total pool size, so the vault eventually empties) + // if we only ever take 1% of the vault, the vault may never empty. + uint24 public fee = 1000; // % expressed as uint < 1e6 + + int24 public lowerTick; + int24 public upperTick; + + function setUp() public { + token0 = new TestERC20(2**128); + token1 = new TestERC20(2**128); + + if (uint160(address(token0)) > uint160(address(token1))) { + TestERC20 temp = token0; + token0 = token1; + token1 = temp; + } + + manager = new PoolManager(500000); + + DiamondHookImpl impl = new DiamondHookImpl(manager, hook,tickSpacing,baseBeta,decayRate,vaultRedepositRate); + (, bytes32[] memory writes) = vm.accesses(address(impl)); + vm.etch(address(hook), address(impl).code); + // for each storage key that was written during the hook implementation, copy the value over + unchecked { + for (uint256 i = 0; i < writes.length; i++) { + bytes32 slot = writes[i]; + vm.store(address(hook), slot, vm.load(address(impl), slot)); + } + } + // Create the pool + poolKey = PoolKey( + Currency.wrap(address(token0)), + Currency.wrap(address(token1)), + fee, + tickSpacing, + hook + ); + poolId = poolKey.toId(); + uint256 price = 1; + manager.initialize(poolKey, computeNewSQRTPrice(price)); + swapRouter = new PoolSwapTest(manager); + token0.approve(address(hook), type(uint256).max); + token1.approve(address(hook), type(uint256).max); + token0.approve(address(swapRouter), type(uint256).max); + token1.approve(address(swapRouter), type(uint256).max); + lowerTick = hook.lowerTick(); + upperTick = hook.upperTick(); + + assertEq(lowerTick, -887270); + assertEq(upperTick, 887270); + } + + function testOpeningTotalSupplyZero() public { + uint256 height = 1; + vm.roll(height); + + // do arb swap (price rises from initial) + uint160 newSQRTPrice = computeNewSQRTPrice(4); + + vm.expectRevert( + abi.encodeWithSelector( + DiamondHookPoC.TotalSupplyZero.selector + ) + ); + + hook.openPool(newSQRTPrice); + } + + function testBasicArbSwap() public { + // mint some liquidity + hook.mint(10**18,address(this)); + uint256 height = 1; + vm.roll(height); + + // get starting values + uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); + uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); + uint128 liquidityBefore = manager.getLiquidity(poolId); + uint256 balance0ThisBefore = token0.balanceOf(address(this)); + uint256 balance1ThisBefore = token1.balanceOf(address(this)); + + // do arb swap (price rises from initial) + uint160 newSQRTPrice = computeNewSQRTPrice(4); + hook.openPool(newSQRTPrice); + + // get ending values + (uint160 newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); + uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); + uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); + uint128 liquidityAfter = manager.getLiquidity(poolId); + uint256 balance0ThisAfter = token0.balanceOf(address(this)); + uint256 balance1ThisAfter = token1.balanceOf(address(this)); + + // check expectations + assertEq(newSQRTPriceCheck, newSQRTPrice); // pool price moved to where arb swap specified + assertGt(balance1ManagerAfter, balance1ManagerBefore); // pool gained token0 + assertGt(balance0ManagerBefore, balance0ManagerAfter); // pool lost token1 + assertGt(balance1ThisBefore, balance1ThisAfter); // arber lost token0 + assertGt(balance0ThisAfter, balance0ThisBefore); // arber gained token1 + assertEq(balance0ThisAfter-balance0ThisBefore, balance0ManagerBefore-balance0ManagerAfter); // net is 0 + assertEq(balance1ThisBefore-balance1ThisAfter, balance1ManagerAfter-balance1ManagerBefore); // net is 0 + assertGt(liquidityBefore, liquidityAfter); // liquidity decreased + + // reset starting values + balance0ThisBefore = balance0ThisAfter; + balance1ThisBefore = balance1ThisAfter; + balance0ManagerBefore = balance0ManagerAfter; + balance1ManagerBefore = balance1ManagerAfter; + + // go to next block + vm.roll(height+1); + + // do arb swap (price back down to initial) + newSQRTPrice = computeNewSQRTPrice(1); + hook.openPool(newSQRTPrice); + + // get ending values + balance0ThisAfter = token0.balanceOf(address(this)); + balance1ThisAfter = token1.balanceOf(address(this)); + balance0ManagerAfter = token0.balanceOf(address(manager)); + balance1ManagerAfter = token1.balanceOf(address(manager)); + (newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); + + // check expectations + assertEq(newSQRTPrice, newSQRTPriceCheck); + assertGt(balance1ManagerBefore, balance1ManagerAfter); + assertGt(balance0ManagerAfter, balance0ManagerBefore); + assertGt(balance1ThisAfter, balance1ThisBefore); + assertGt(balance0ThisBefore, balance0ThisAfter); + assertEq(balance1ThisAfter-balance1ThisBefore, balance1ManagerBefore-balance1ManagerAfter); + assertEq(balance0ThisBefore-balance0ThisAfter, balance0ManagerAfter-balance0ManagerBefore); + + uint160 liquidityAfter2 = manager.getLiquidity(poolId); + assertGt(liquidityAfter2, liquidityAfter); // liquidity actually increased (price moved back, can redeposit more vault) + assertGt(liquidityBefore, liquidityAfter2); // but liquidity still less than originally + } + + function testManyWhipSaws() public { + hook.mint(10**18,address(this)); + uint256 height = 1; + uint256 price; + uint160 newSQRTPrice; + + uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); + uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); + (uint256 liquidity0Before, uint256 liquidity1Before) = getTokenReservesInPool(); + + assertEq(balance0ManagerBefore-1, liquidity0Before); + assertEq(balance1ManagerBefore-1, liquidity1Before); + + for(uint256 i = 0; i < 5; i++) { + vm.roll(height++); + price = 4; + newSQRTPrice=computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + vm.roll(height++); + price = 1; + newSQRTPrice=computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + } + + uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); + uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); + (uint256 liquidity0After, uint256 liquidity1After) = getTokenReservesInPool(); + + assertGt(liquidity0Before, liquidity0After); + assertGt(liquidity1Before, liquidity1After); + + uint256 undeposited0 = balance0ManagerAfter - liquidity0After; + uint256 undeposited1 = balance1ManagerAfter - liquidity1After; + uint256 dustThreshold = 100; + + // should still have deposited almost ALL of of one or the other token (modulo some dust) + assertGt(dustThreshold, undeposited0 > undeposited1 ? undeposited1 : undeposited0); + } + + function testWithdraw() public { + hook.mint(10**18,address(this)); + uint256 totalSupply1 = hook.totalSupply(); + assertEq(totalSupply1, 10**18); + + uint256 height = 1; + uint256 price = 4; + uint160 newSQRTPrice = computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + + hook.burn(10**16, address(this)); + + uint256 totalSupply2 = hook.totalSupply(); + assertEq(totalSupply2, totalSupply1-10**16); + + hook.burn(totalSupply2, address(this)); + assertEq(hook.totalSupply(), 0); + uint256 balance0Manager = token0.balanceOf(address(manager)); + uint256 balance1Manager = token1.balanceOf(address(manager)); + + // console.log(balance0Manager); + // console.log(balance1Manager); + + // this is kind of high IMO we are already somehow losing 3 wei in both tokens + // seems like we may somehow losing track of 1 wei into the contract + // not just for every openPool() but on other ops too? + uint256 dustThreshold = 4; + assertGt(dustThreshold, balance0Manager); + assertGt(dustThreshold, balance1Manager); + + hook.mint(10**18, address(this)); + + vm.roll(++height); + price = 2; + newSQRTPrice=computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + + // test mint/burn invariant (you get back as much as you put in if nothing else changes (no swaps etc) + uint256 balance0Before = token0.balanceOf(address(this)); + uint256 balance1Before = token1.balanceOf(address(this)); + + //console.log("midway balances:",token0.balanceOf(address(manager)),token1.balanceOf(address(manager))); + hook.mint(10**18, address(this)); + hook.burn(10**18, address(this)); + hook.mint(10**24, address(this)); + hook.burn(10**24, address(this)); + + // NOTE this invariant is not working amounts are slightly off!!!! + //assertEq(token0.balanceOf(address(this)), balance0Before); + //assertEq(token1.balanceOf(address(this)), balance1Before); + + vm.roll(++height); + price = 4; + newSQRTPrice=computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + + hook.burn(10**10, address(this)); + vm.roll(++height); + balance0Before = token0.balanceOf(address(this)); + balance1Before = token1.balanceOf(address(this)); + //console.log("Let's try a TOB mint"); + // (uint160 poolPrice,,,,,)=manager.getSlot0(poolId); + // (uint256 x, uint256 y)=getTokenReservesInPool(); + // console.log("new block, pre-mint. reserves:",x,y,poolPrice); + // console.log("new block, pre-mint. pool-price:",poolPrice); + + hook.mint(10**12, address(this)); + // (poolPrice,,,,,)=manager.getSlot0(poolId); + // (x, y)=getTokenReservesInPool(); + // console.log("new block, post-mint. reserves:",x,y,poolPrice); + // console.log("new block, post-mint. pool-price:",poolPrice); + hook.openPool(newSQRTPrice); + //console.log("before and after difference token 0:",token0.balanceOf(address(this))-balance0Before); + //console.log("before and after difference token 1:",token1.balanceOf(address(this))-balance1Before); + + vm.roll(++height); + // (poolPrice,,,,,)=manager.getSlot0(poolId); + // (x, y)=getTokenReservesInPool(); + // console.log("new block, pre-burn. reserves:",x,y,poolPrice); + // console.log("new block, pre-burn. pool-price:",poolPrice); + hook.burn(10**12, address(this)); + // (poolPrice,,,,,)=manager.getSlot0(poolId); + // (x, y)=getTokenReservesInPool(); + // console.log("new block, post-burn. reserves:",x,y,poolPrice); + // console.log("new block, post-burn. pool-price:",poolPrice); + hook.openPool(newSQRTPrice); + + hook.burn(hook.totalSupply(), address(this)); + assertEq(hook.totalSupply(), 0); + + + balance0Manager = token0.balanceOf(address(manager)); + balance1Manager = token1.balanceOf(address(manager)); + dustThreshold = 18; + assertGt(dustThreshold, balance0Manager); + assertGt(dustThreshold, balance1Manager); + } + + function testSwaps() public { + hook.mint(10**20,address(this)); + uint256 height = 1; + uint256 price = 4; + uint160 newSQRTPrice = computeNewSQRTPrice(price); + + hook.openPool(newSQRTPrice); + uint128 hedgeCommit0=10**18; + uint128 hedgeCommit1=10**18; + + // must deposit hedge tokens before swap can take place + hook.depositHedgeCommitment(hedgeCommit0, hedgeCommit1); + + // prepare swap token0 for token1 + IPoolManager.SwapParams memory params = + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10**15, sqrtPriceLimitX96:computeNewSQRTPrice(3)}); + PoolSwapTest.TestSettings memory settings = + PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + + uint256 balance0Before = token0.balanceOf(address(this)); + uint256 balance1Before = token1.balanceOf(address(this)); + + swapRouter.swap(poolKey, params, settings); + + uint256 balance0After = token0.balanceOf(address(this)); + uint256 balance1After = token1.balanceOf(address(this)); + + uint256 hedgeRequired0 = hook.hedgeRequired0(); + uint256 hedgeRequired1 = hook.hedgeRequired1(); + + assertGt(balance0Before, balance0After); + assertGt(balance1After, balance1Before); + assertGt(hedgeRequired1, 0); + assertEq(hedgeRequired0, 0); + assertEq(balance1After-balance1Before, hedgeRequired1); + + // now the swapper is going to sell token 1s for 0s back to the pool + // in approx the same size as before + // to move the pool price back to original price + params = IPoolManager.SwapParams({zeroForOne: false, amountSpecified: -int256(hedgeRequired1), sqrtPriceLimitX96:computeNewSQRTPrice(5)}); + + balance0Before = token0.balanceOf(address(this)); + balance1Before = token1.balanceOf(address(this)); + + swapRouter.swap(poolKey, params, settings); + + balance0After = token0.balanceOf(address(this)); + balance1After = token1.balanceOf(address(this)); + + hedgeRequired0 = hook.hedgeRequired0(); + hedgeRequired1 = hook.hedgeRequired1(); + + assertGt(balance1Before, balance1After); + assertGt(balance0After, balance0Before); + assertGt(hedgeRequired0, 0); + assertEq(hedgeRequired1, 0); + + hook.withdrawHedgeCommitment(hedgeCommit0-hedgeRequired0, hedgeCommit1); + + params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 2**80, sqrtPriceLimitX96:computeNewSQRTPrice(4)}); + + balance0Before = token0.balanceOf(address(this)); + balance1Before = token1.balanceOf(address(this)); + + swapRouter.swap(poolKey, params, settings); + + balance0After = token0.balanceOf(address(this)); + balance1After = token1.balanceOf(address(this)); + + hedgeRequired0 = hook.hedgeRequired0(); + hedgeRequired1 = hook.hedgeRequired1(); + + assertGt(balance0Before, balance0After); + assertGt(balance1After, balance1Before); + assertEq(hedgeRequired0, 0); + assertEq(hedgeRequired1, 0); + + hook.withdrawHedgeCommitment(hook.hedgeCommitted0(), 0); + + assertEq(hook.hedgeCommitted0(), 0); + assertEq(hook.hedgeCommitted1(), 0); + + vm.roll(++height); + price=2; + newSQRTPrice=computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + } + + function _sqrt(uint256 x) internal pure returns (uint256 y) { + uint256 z = (x + 1) / 2; + y = x; + while (z < y) { + y = z; + z = (x / z + z) / 2; + } + } + + function computeNewSQRTPrice(uint256 price) internal pure returns (uint160 y){ + y=uint160(_sqrt(price*2**96)*2**48); + } + + function getTokenReservesInPool() public view returns (uint256 x, uint256 y){ + Position.Info memory info = manager.getPosition(poolId, address(hook), lowerTick, upperTick); + (uint160 poolPrice,,,,,)= manager.getSlot0(poolId); + (x, y) = LiquidityAmounts.getAmountsForLiquidity( + poolPrice, + TickMath.getSqrtRatioAtTick(lowerTick), + TickMath.getSqrtRatioAtTick(upperTick), + info.liquidity + ); + } +} \ No newline at end of file diff --git a/test/utils/DiamondHookImpl.sol b/test/utils/DiamondHookImpl.sol new file mode 100644 index 0000000..619606e --- /dev/null +++ b/test/utils/DiamondHookImpl.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.8.20; + +import {DiamondHookPoC} from "../../src/DiamondHookFutures.sol"; + +import {BaseHook} from "@uniswap/v4-periphery/contracts/BaseHook.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; + +contract DiamondHookImpl is DiamondHookPoC{ + constructor(IPoolManager poolManager, DiamondHookPoC addressToEtch, int24 tickSpacing, uint24 baseBeta, uint24 decayRate, uint24 vaultRedepositRate) DiamondHookPoC(poolManager,tickSpacing,baseBeta,decayRate,vaultRedepositRate) { + Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + } + + // make this a no-op in testing + function validateHookAddress(BaseHook _this) internal pure override {} +} \ No newline at end of file From e269dbbb76a9d173ecf77d7858956d3262a1cee2 Mon Sep 17 00:00:00 2001 From: sm-stack Date: Wed, 3 Jan 2024 18:28:12 +0900 Subject: [PATCH 2/6] Update lib --- lib/periphery-next | 1 - 1 file changed, 1 deletion(-) delete mode 160000 lib/periphery-next diff --git a/lib/periphery-next b/lib/periphery-next deleted file mode 160000 index 63d64fc..0000000 --- a/lib/periphery-next +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 63d64fcd82bff9ec0bad89730ce28d7ffa8e4225 From 3f78096c498260f3c88ab99e4fb797d4b1e06a20 Mon Sep 17 00:00:00 2001 From: sm-stack Date: Wed, 3 Jan 2024 18:28:20 +0900 Subject: [PATCH 3/6] forge install: periphery-next --- lib/periphery-next | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/periphery-next diff --git a/lib/periphery-next b/lib/periphery-next new file mode 160000 index 0000000..63d64fc --- /dev/null +++ b/lib/periphery-next @@ -0,0 +1 @@ +Subproject commit 63d64fcd82bff9ec0bad89730ce28d7ffa8e4225 From 920652ae2e6bd1a35b629fcd3a6f19fb3a3bee2d Mon Sep 17 00:00:00 2001 From: sm-stack Date: Thu, 4 Jan 2024 03:18:43 +0900 Subject: [PATCH 4/6] Update dependencies to older commits --- lib/periphery-next | 2 +- lib/v4-core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/periphery-next b/lib/periphery-next index 63d64fc..74dab0c 160000 --- a/lib/periphery-next +++ b/lib/periphery-next @@ -1 +1 @@ -Subproject commit 63d64fcd82bff9ec0bad89730ce28d7ffa8e4225 +Subproject commit 74dab0c398ee9e07aa4e8a8836709eb599576157 diff --git a/lib/v4-core b/lib/v4-core index 1f350fa..3921fad 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 1f350fa95e862ba8c56c8ff7e146d47c9043465e +Subproject commit 3921fad6eb075b7853258a54fae2ce0cb5ba40b0 From b94cf4cc58a967cf0d55971ddde9ca544e88d2cf Mon Sep 17 00:00:00 2001 From: sm-stack Date: Thu, 4 Jan 2024 03:20:02 +0900 Subject: [PATCH 5/6] Fix incompatible codes --- src/BaseFactory.sol | 4 +- src/DiamondHookFutures.sol | 720 ++++++++---------------- src/libraries/LiquidityAmounts.sol | 149 ++--- test/DiamondHook.t.sol | 872 ++++++++++++++--------------- test/utils/DiamondHookImpl.sol | 15 +- 5 files changed, 721 insertions(+), 1039 deletions(-) diff --git a/src/BaseFactory.sol b/src/BaseFactory.sol index 5b64a95..b9f55be 100644 --- a/src/BaseFactory.sol +++ b/src/BaseFactory.sol @@ -18,7 +18,7 @@ abstract contract BaseFactory { function deploy(IPoolManager poolManager, bytes32 salt) public virtual returns (address); function mineDeploy(IPoolManager poolManager) external returns (address) { - return deploy(poolManager,0); + return deploy(poolManager, 0); } function _computeHookAddress(IPoolManager poolManager, bytes32 salt) internal view returns (address) { @@ -36,4 +36,4 @@ abstract contract BaseFactory { address actualPrefix = address(uint160(_address) & UNISWAP_FLAG_MASK); return actualPrefix == TargetPrefix; } -} \ No newline at end of file +} diff --git a/src/DiamondHookFutures.sol b/src/DiamondHookFutures.sol index e3efa92..5baed93 100644 --- a/src/DiamondHookFutures.sol +++ b/src/DiamondHookFutures.sol @@ -24,7 +24,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {BaseFactory} from "./BaseFactory.sol"; -contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { +contract DiamondHookFutures is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { using PoolIdLibrary for PoolKey; using CurrencyLibrary for Currency; using BalanceDeltaLibrary for BalanceDelta; @@ -79,11 +79,13 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { bool public initialized; struct PoolManagerCalldata { - uint256 amount; /// mintAmount | burnAmount | newSqrtPriceX96 (inferred from actionType) + uint256 amount; + /// mintAmount | burnAmount | newSqrtPriceX96 (inferred from actionType) address msgSender; address receiver; - uint8 actionType; /// 0 = mint | 1 = burn | 2 = arbSwap + uint8 actionType; } + /// 0 = mint | 1 = burn | 2 = arbSwap struct ArbSwapParams { uint160 sqrtPriceX96; @@ -104,46 +106,24 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { lowerTick = _tickSpacing.minUsableTick(); upperTick = _tickSpacing.maxUsableTick(); tickSpacing = _tickSpacing; - require( - _baseBeta < _PIPS && - _decayRate <= _baseBeta && - _vaultRedepositRate < _PIPS - ); + require(_baseBeta < _PIPS && _decayRate <= _baseBeta && _vaultRedepositRate < _PIPS); baseBeta = _baseBeta; decayRate = _decayRate; vaultRedepositRate = _vaultRedepositRate; } - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes calldata - ) external view returns (bytes4) { + function onERC1155Received(address, address, uint256, uint256, bytes calldata) external view returns (bytes4) { if (msg.sender != address(poolManager)) revert NotPoolManagerToken(); - return - bytes4( - keccak256( - "onERC1155Received(address,address,uint256,uint256,bytes)" - ) - ); + return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")); } - function onERC1155BatchReceived( - address, - address, - uint256[] calldata, - uint256[] calldata, - bytes calldata - ) external view returns (bytes4) { + function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) + external + view + returns (bytes4) + { if (msg.sender != address(poolManager)) revert NotPoolManagerToken(); - return - bytes4( - keccak256( - "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)" - ) - ); + return bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")); } function supportsInterface(bytes4) external pure returns (bool) { @@ -151,24 +131,23 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { } function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return - Hooks.Calls({ - beforeInitialize: true, - afterInitialize: false, - beforeModifyPosition: true, - afterModifyPosition: false, - beforeSwap: true, - afterSwap: true, - beforeDonate: false, - afterDonate: false - }); - } - - function beforeInitialize( - address, - PoolKey calldata poolKey_, - uint160 sqrtPriceX96 - ) external override returns (bytes4) { + return Hooks.Calls({ + beforeInitialize: true, + afterInitialize: false, + beforeModifyPosition: true, + afterModifyPosition: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false + }); + } + + function beforeInitialize(address, PoolKey calldata poolKey_, uint160 sqrtPriceX96, bytes calldata) + external + override + returns (bytes4) + { /// can only initialize one pool once. if (initialized) revert AlreadyInitialized(); @@ -186,11 +165,12 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { return this.beforeInitialize.selector; } - function beforeSwap( - address sender, - PoolKey calldata, - IPoolManager.SwapParams calldata - ) external view override returns (bytes4) { + function beforeSwap(address sender, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + external + view + override + returns (bytes4) + { /// if swap is coming from the hook then its a 1 wei swap to kick the price and not a "normal" swap if (sender != address(this)) { /// disallow normal swaps at top of block @@ -199,43 +179,30 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { return BaseHook.beforeSwap.selector; } - function afterSwap( - address sender, - PoolKey calldata, - IPoolManager.SwapParams calldata, - BalanceDelta - ) external override returns (bytes4) { + function afterSwap(address sender, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { /// if swap is coming from the hook then its a 1 wei swap to kick the price and not a "normal" swap if (sender != address(this)) { /// cannot move price to edge of LP positin PoolId poolId = PoolIdLibrary.toId(poolKey); - (uint160 sqrtPriceX96, , , , , ) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); uint160 sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(lowerTick); uint160 sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(upperTick); - if ( - sqrtPriceX96 >= sqrtPriceX96Upper || - sqrtPriceX96 <= sqrtPriceX96Lower - ) revert PriceOutOfBounds(); - - Position.Info memory info = PoolManager( - payable(address(poolManager)) - ).getPosition(poolId, address(this), lowerTick, upperTick); - - (uint256 current0, uint256 current1) = LiquidityAmounts - .getAmountsForLiquidity( - sqrtPriceX96, - sqrtPriceX96Lower, - sqrtPriceX96Upper, - info.liquidity - ); + if (sqrtPriceX96 >= sqrtPriceX96Upper || sqrtPriceX96 <= sqrtPriceX96Lower) revert PriceOutOfBounds(); - (uint256 need0, uint256 need1) = LiquidityAmounts - .getAmountsForLiquidity( - committedSqrtPriceX96, - sqrtPriceX96Lower, - sqrtPriceX96Upper, - info.liquidity - ); + Position.Info memory info = + PoolManager(payable(address(poolManager))).getPosition(poolId, address(this), lowerTick, upperTick); + + (uint256 current0, uint256 current1) = LiquidityAmounts.getAmountsForLiquidity( + sqrtPriceX96, sqrtPriceX96Lower, sqrtPriceX96Upper, info.liquidity + ); + + (uint256 need0, uint256 need1) = LiquidityAmounts.getAmountsForLiquidity( + committedSqrtPriceX96, sqrtPriceX96Lower, sqrtPriceX96Upper, info.liquidity + ); if (need0 > current0) { uint256 min0 = need0 - current0; @@ -256,26 +223,22 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { return BaseHook.afterSwap.selector; } - function beforeModifyPosition( - address sender, - PoolKey calldata, - IPoolManager.ModifyPositionParams calldata - ) external view override returns (bytes4) { + function beforeModifyPosition(address sender, PoolKey calldata, IPoolManager.ModifyPositionParams calldata, bytes calldata) + external + view + override + returns (bytes4) + { /// force LPs to provide liquidity through hook if (sender != address(this)) revert OnlyModifyViaHook(); return BaseHook.beforeModifyPosition.selector; } /// method called back on PoolManager.lock() - function lockAcquired( - bytes calldata data_ - ) external override poolManagerOnly returns (bytes memory) { + function lockAcquired(bytes calldata data_) external override poolManagerOnly returns (bytes memory) { /// decode calldata passed through lock() - PoolManagerCalldata memory pmCalldata = abi.decode( - data_, - (PoolManagerCalldata) - ); + PoolManagerCalldata memory pmCalldata = abi.decode(data_, (PoolManagerCalldata)); /// first case mint action if (pmCalldata.actionType == 0) _lockAcquiredMint(pmCalldata); /// second case burn action @@ -287,7 +250,7 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { /// anyone can call this method to "open the pool" with top of block arb swap. /// no swaps will be processed in a block unless this method is called first in that block. function openPool(uint160 newSqrtPriceX96_) external payable nonReentrant { - if(totalSupply() == 0) revert TotalSupplyZero(); + if (totalSupply() == 0) revert TotalSupplyZero(); /// encode calldata to pass through lock() bytes memory data = abi.encode( @@ -295,9 +258,10 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { amount: uint256(newSqrtPriceX96_), msgSender: msg.sender, receiver: msg.sender, - actionType: 2 /// arbSwap action + actionType: 2 }) ); + /// arbSwap action /// begin pool actions (passing data through lock() into _lockAcquiredArb()) poolManager.lock(data); @@ -317,21 +281,14 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { } } - function depositHedgeCommitment( - uint256 amount0, - uint256 amount1 - ) external payable { + function depositHedgeCommitment(uint256 amount0, uint256 amount1) external payable { if (lastBlockOpened != block.number) revert PoolNotOpen(); if (amount0 > 0) { if (poolKey.currency0.isNative()) { if (msg.value != amount0) revert InvalidMsgValue(); } else { - ERC20(Currency.unwrap(poolKey.currency0)).safeTransferFrom( - msg.sender, - address(this), - amount0 - ); + ERC20(Currency.unwrap(poolKey.currency0)).safeTransferFrom(msg.sender, address(this), amount0); } hedgeCommitted0 += amount0; } @@ -340,71 +297,52 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { if (poolKey.currency1.isNative()) { if (msg.value != amount1) revert InvalidMsgValue(); } else { - ERC20(Currency.unwrap(poolKey.currency1)).safeTransferFrom( - msg.sender, - address(this), - amount1 - ); + ERC20(Currency.unwrap(poolKey.currency1)).safeTransferFrom(msg.sender, address(this), amount1); } hedgeCommitted1 += amount1; } } - function withdrawHedgeCommitment( - uint256 amount0, - uint256 amount1 - ) external nonReentrant { + function withdrawHedgeCommitment(uint256 amount0, uint256 amount1) external nonReentrant { if (committer != msg.sender) revert OnlyCommitter(); if (amount0 > 0) { - uint256 withdrawAvailable0 = hedgeRequired0 > 0 - ? hedgeCommitted0 - hedgeRequired0 - : hedgeCommitted0; + uint256 withdrawAvailable0 = hedgeRequired0 > 0 ? hedgeCommitted0 - hedgeRequired0 : hedgeCommitted0; if (amount0 > withdrawAvailable0) revert WithdrawExceedsAvailable(); hedgeCommitted0 -= amount0; if (poolKey.currency0.isNative()) { _nativeTransfer(msg.sender, amount0); } else { - ERC20(Currency.unwrap(poolKey.currency0)).safeTransfer( - msg.sender, - amount0 - ); + ERC20(Currency.unwrap(poolKey.currency0)).safeTransfer(msg.sender, amount0); } } if (amount1 > 0) { - uint256 withdrawAvailable1 = hedgeRequired1 > 0 - ? hedgeCommitted1 - hedgeRequired1 - : hedgeCommitted1; + uint256 withdrawAvailable1 = hedgeRequired1 > 0 ? hedgeCommitted1 - hedgeRequired1 : hedgeCommitted1; if (amount1 > withdrawAvailable1) revert WithdrawExceedsAvailable(); hedgeCommitted1 -= amount1; if (poolKey.currency1.isNative()) { _nativeTransfer(msg.sender, amount1); } else { - ERC20(Currency.unwrap(poolKey.currency1)).safeTransfer( - msg.sender, - amount1 - ); + ERC20(Currency.unwrap(poolKey.currency1)).safeTransfer(msg.sender, amount1); } } } /// how LPs add and remove liquidity into the hook - function mint( - uint256 mintAmount_, - address receiver_ - ) external payable nonReentrant returns (uint256 amount0, uint256 amount1) { + function mint(uint256 mintAmount_, address receiver_) + external + payable + nonReentrant + returns (uint256 amount0, uint256 amount1) + { if (mintAmount_ == 0) revert MintZero(); /// encode calldata to pass through lock() bytes memory data = abi.encode( - PoolManagerCalldata({ - amount: mintAmount_, - msgSender: msg.sender, - receiver: receiver_, - actionType: 0 /// mint action - }) + PoolManagerCalldata({amount: mintAmount_, msgSender: msg.sender, receiver: receiver_, actionType: 0}) ); + /// mint action /// state variables to be able to bubble up amount0 and amount1 as return args _a0 = _a1 = 0; @@ -429,10 +367,11 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { _mint(receiver_, mintAmount_); } - function burn( - uint256 burnAmount_, - address receiver_ - ) external nonReentrant returns (uint256 amount0, uint256 amount1) { + function burn(uint256 burnAmount_, address receiver_) + external + nonReentrant + returns (uint256 amount0, uint256 amount1) + { if (burnAmount_ == 0) revert BurnZero(); if (totalSupply() < burnAmount_) revert BurnExceedsSupply(); @@ -463,12 +402,8 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { function _lockAcquiredArb(PoolManagerCalldata memory pmCalldata) internal { uint256 blockDelta = _checkLastOpen(); - ( - uint160 sqrtPriceX96Real, - uint160 sqrtPriceX96Virtual, - uint128 liquidityReal, - uint128 liquidityVirtual - ) = _resetLiquidity(false); + (uint160 sqrtPriceX96Real, uint160 sqrtPriceX96Virtual, uint128 liquidityReal, uint128 liquidityVirtual) = + _resetLiquidity(false); uint160 newSqrtPriceX96 = SafeCast.toUint160(pmCalldata.amount); @@ -492,12 +427,11 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { poolManager.modifyPosition( poolKey, IPoolManager.ModifyPositionParams({ - liquidityDelta: -SafeCast.toInt256( - uint256(liquidityReal) - ), + liquidityDelta: -SafeCast.toInt256(uint256(liquidityReal)), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); _clear1155Balances(); } @@ -511,19 +445,15 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { zeroForOne: newSqrtPriceX96 < sqrtPriceX96Real, amountSpecified: 1, sqrtPriceLimitX96: newSqrtPriceX96 - }) + }), + new bytes(0) ); } /// handle swap transfers (send to / transferFrom arber) if (zeroForOne) { /// transfer swapInAmt to PoolManager - _transferFromOrTransferNative( - poolKey.currency0, - pmCalldata.msgSender, - address(poolManager), - swap0 - ); + _transferFromOrTransferNative(poolKey.currency0, pmCalldata.msgSender, address(poolManager), swap0); poolManager.settle(poolKey.currency0); /// transfer swapOutAmt to arber @@ -531,12 +461,7 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { } else { /// transfer swapInAmt to PoolManager - _transferFromOrTransferNative( - poolKey.currency1, - pmCalldata.msgSender, - address(poolManager), - swap1 - ); + _transferFromOrTransferNative(poolKey.currency1, pmCalldata.msgSender, address(poolManager), swap1); poolManager.settle(poolKey.currency1); /// transfer swapOutAmt to arber @@ -544,17 +469,10 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { } } - ( - uint256 totalHoldings0, - uint256 totalHoldings1 - ) = _checkCurrencyBalances(); + (uint256 totalHoldings0, uint256 totalHoldings1) = _checkCurrencyBalances(); uint128 newLiquidity = LiquidityAmounts.getLiquidityForAmounts( - newSqrtPriceX96, - sqrtPriceX96Lower, - sqrtPriceX96Upper, - totalHoldings0, - totalHoldings1 + newSqrtPriceX96, sqrtPriceX96Lower, sqrtPriceX96Upper, totalHoldings0, totalHoldings1 ); /// mint new liquidity around newSqrtPriceX96 @@ -564,7 +482,8 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { liquidityDelta: SafeCast.toInt256(uint256(newLiquidity)), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); /// if any positive balances remain in PoolManager after all operations, mint erc1155 shares @@ -581,114 +500,77 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { liquidityDelta: SafeCast.toInt256(pmCalldata.amount), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); // casting to uint256 is ok for minting - _a0 = SafeCast.toUint256( - poolManager.currencyDelta(address(this), poolKey.currency0) - ); - _a1 = SafeCast.toUint256( - poolManager.currencyDelta(address(this), poolKey.currency1) - ); + _a0 = SafeCast.toUint256(poolManager.currencyDelta(address(this), poolKey.currency0)); + _a1 = SafeCast.toUint256(poolManager.currencyDelta(address(this), poolKey.currency1)); if (_a0 > 0) { - _transferFromOrTransferNative( - poolKey.currency0, - pmCalldata.msgSender, - address(poolManager), - _a0 - ); + _transferFromOrTransferNative(poolKey.currency0, pmCalldata.msgSender, address(poolManager), _a0); poolManager.settle(poolKey.currency0); } if (_a1 > 0) { - _transferFromOrTransferNative( - poolKey.currency1, - pmCalldata.msgSender, - address(poolManager), - _a1 - ); + _transferFromOrTransferNative(poolKey.currency1, pmCalldata.msgSender, address(poolManager), _a1); poolManager.settle(poolKey.currency1); } } else { uint128 liquidity; /// if this is first touch in this block, then we need to _resetLiquidity() first if (lastBlockOpened != block.number) { - (, , liquidity, ) = _resetLiquidity(true); + (,, liquidity,) = _resetLiquidity(true); } else { - Position.Info memory info = PoolManager( - payable(address(poolManager)) - ).getPosition( - PoolIdLibrary.toId(poolKey), - address(this), - lowerTick, - upperTick - ); + Position.Info memory info = PoolManager(payable(address(poolManager))).getPosition( + PoolIdLibrary.toId(poolKey), address(this), lowerTick, upperTick + ); liquidity = info.liquidity; } - if (liquidity > 0) + if (liquidity > 0) { poolManager.modifyPosition( poolKey, IPoolManager.ModifyPositionParams({ liquidityDelta: -SafeCast.toInt256(uint256(liquidity)), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); + } _checkCurrencyBalances(); - (, uint256 leftOver0, , uint256 leftOver1) = _get1155Balances(); + (uint256 leftOver0, uint256 leftOver1) = _get1155Balances(); // mint back the position. - uint256 newLiquidity = liquidity + - FullMath.mulDiv(pmCalldata.amount, liquidity, totalSupply); + uint256 newLiquidity = liquidity + FullMath.mulDiv(pmCalldata.amount, liquidity, totalSupply); - if (newLiquidity > 0) + if (newLiquidity > 0) { poolManager.modifyPosition( poolKey, IPoolManager.ModifyPositionParams({ liquidityDelta: SafeCast.toInt256(newLiquidity), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); + } - uint256 amount0 = SafeCast.toUint256( - poolManager.currencyDelta(address(this), poolKey.currency0) - ); - uint256 amount1 = SafeCast.toUint256( - poolManager.currencyDelta(address(this), poolKey.currency1) - ); + uint256 amount0 = SafeCast.toUint256(poolManager.currencyDelta(address(this), poolKey.currency0)); + uint256 amount1 = SafeCast.toUint256(poolManager.currencyDelta(address(this), poolKey.currency1)); - amount0 += FullMath.mulDivRoundingUp( - leftOver0, - pmCalldata.amount, - totalSupply - ); - amount1 += FullMath.mulDivRoundingUp( - leftOver1, - pmCalldata.amount, - totalSupply - ); + amount0 += FullMath.mulDivRoundingUp(leftOver0, pmCalldata.amount, totalSupply); + amount1 += FullMath.mulDivRoundingUp(leftOver1, pmCalldata.amount, totalSupply); if (amount0 > 0) { - _transferFromOrTransferNative( - poolKey.currency0, - pmCalldata.msgSender, - address(poolManager), - amount0 - ); + _transferFromOrTransferNative(poolKey.currency0, pmCalldata.msgSender, address(poolManager), amount0); poolManager.settle(poolKey.currency0); } if (amount1 > 0) { - _transferFromOrTransferNative( - poolKey.currency1, - pmCalldata.msgSender, - address(poolManager), - amount1 - ); + _transferFromOrTransferNative(poolKey.currency1, pmCalldata.msgSender, address(poolManager), amount1); poolManager.settle(poolKey.currency1); } @@ -698,23 +580,13 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { _mintLeftover(); if (hedgeRequired0 > 0) { - hedgeRequired0 += FullMath.mulDiv( - hedgeRequired0, - pmCalldata.amount, - totalSupply - ); + hedgeRequired0 += FullMath.mulDiv(hedgeRequired0, pmCalldata.amount, totalSupply); } if (hedgeRequired1 > 0) { - hedgeRequired1 += FullMath.mulDiv( - hedgeRequired1, - pmCalldata.amount, - totalSupply - ); + hedgeRequired1 += FullMath.mulDiv(hedgeRequired1, pmCalldata.amount, totalSupply); } - if ( - hedgeRequired0 > hedgeCommitted0 || hedgeRequired1 > hedgeCommitted1 - ) revert InsufficientHedgeCommitted(); + if (hedgeRequired0 > hedgeCommitted0 || hedgeRequired1 > hedgeCommitted1) revert InsufficientHedgeCommitted(); } // this function gets the supply of LP tokens, the supply of LP tokens to removes, @@ -727,63 +599,47 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { uint128 liquidity; /// if this is first touch in this block, then we need to _resetLiquidity() first if (lastBlockOpened != block.number) { - (, , liquidity, ) = _resetLiquidity(true); + (,, liquidity,) = _resetLiquidity(true); } else { - Position.Info memory info = PoolManager( - payable(address(poolManager)) - ).getPosition( - PoolIdLibrary.toId(poolKey), - address(this), - lowerTick, - upperTick - ); + Position.Info memory info = PoolManager(payable(address(poolManager))).getPosition( + PoolIdLibrary.toId(poolKey), address(this), lowerTick, upperTick + ); liquidity = info.liquidity; } - if (liquidity > 0) + if (liquidity > 0) { poolManager.modifyPosition( poolKey, IPoolManager.ModifyPositionParams({ liquidityDelta: -SafeCast.toInt256(uint256(liquidity)), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); + } _clear1155Balances(); - ( - uint256 currency0Balance, - uint256 currency1Balance - ) = _checkCurrencyBalances(); - uint256 amount0 = FullMath.mulDiv( - pmCalldata.amount, - currency0Balance, - totalSupply - ); - uint256 amount1 = FullMath.mulDiv( - pmCalldata.amount, - currency1Balance, - totalSupply - ); + (uint256 currency0Balance, uint256 currency1Balance) = _checkCurrencyBalances(); + uint256 amount0 = FullMath.mulDiv(pmCalldata.amount, currency0Balance, totalSupply); + uint256 amount1 = FullMath.mulDiv(pmCalldata.amount, currency1Balance, totalSupply); - uint256 newLiquidity = liquidity - - FullMath.mulDiv(pmCalldata.amount, liquidity, totalSupply); + uint256 newLiquidity = liquidity - FullMath.mulDiv(pmCalldata.amount, liquidity, totalSupply); - if (newLiquidity > 0) + if (newLiquidity > 0) { poolManager.modifyPosition( poolKey, IPoolManager.ModifyPositionParams({ liquidityDelta: SafeCast.toInt256(newLiquidity), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); + } - ( - currency0Balance, - currency1Balance - ) = _checkCurrencyBalances(); + (currency0Balance, currency1Balance) = _checkCurrencyBalances(); amount0 = amount0 > currency0Balance ? currency0Balance : amount0; amount1 = amount1 > currency1Balance ? currency1Balance : amount1; @@ -800,18 +656,10 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { _a1 = amount1; _mintLeftover(); if (hedgeRequired0 > 0) { - hedgeRequired0 -= FullMath.mulDiv( - hedgeRequired0, - pmCalldata.amount, - totalSupply - ); + hedgeRequired0 -= FullMath.mulDiv(hedgeRequired0, pmCalldata.amount, totalSupply); } if (hedgeRequired1 > 0) { - hedgeRequired1 -= FullMath.mulDiv( - hedgeRequired1, - pmCalldata.amount, - totalSupply - ); + hedgeRequired1 -= FullMath.mulDiv(hedgeRequired1, pmCalldata.amount, totalSupply); } } @@ -823,47 +671,31 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { return a > b ? a : b; } - function _resetLiquidity( - bool isMintOrBurn - ) + function _resetLiquidity(bool isMintOrBurn) internal - returns ( - uint160 sqrtPriceX96, - uint160 newSqrtPriceX96, - uint128 liquidity, - uint128 newLiquidity - ) + returns (uint160 sqrtPriceX96, uint160 newSqrtPriceX96, uint128 liquidity, uint128 newLiquidity) { - (sqrtPriceX96, , , , , ) = poolManager.getSlot0( - PoolIdLibrary.toId(poolKey) - ); + (sqrtPriceX96,,,) = poolManager.getSlot0(PoolIdLibrary.toId(poolKey)); - Position.Info memory info = PoolManager(payable(address(poolManager))) - .getPosition( - PoolIdLibrary.toId(poolKey), - address(this), - lowerTick, - upperTick - ); + Position.Info memory info = PoolManager(payable(address(poolManager))).getPosition( + PoolIdLibrary.toId(poolKey), address(this), lowerTick, upperTick + ); if (lastBlockReset <= lastBlockOpened) { - if (info.liquidity > 0) + if (info.liquidity > 0) { poolManager.modifyPosition( poolKey, IPoolManager.ModifyPositionParams({ - liquidityDelta: -SafeCast.toInt256( - uint256(info.liquidity) - ), + liquidityDelta: -SafeCast.toInt256(uint256(info.liquidity)), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); + } _clear1155Balances(); - (newSqrtPriceX96, newLiquidity) = _getResetPriceAndLiquidity( - committedSqrtPriceX96, - isMintOrBurn - ); + (newSqrtPriceX96, newLiquidity) = _getResetPriceAndLiquidity(committedSqrtPriceX96, isMintOrBurn); if (isMintOrBurn) { /// swap 1 wei in zero liquidity to kick the price to committedSqrtPriceX96 @@ -874,53 +706,42 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { zeroForOne: newSqrtPriceX96 < sqrtPriceX96, amountSpecified: 1, sqrtPriceLimitX96: newSqrtPriceX96 - }) + }), + new bytes(0) ); } - if (newLiquidity > 0) + if (newLiquidity > 0) { poolManager.modifyPosition( poolKey, IPoolManager.ModifyPositionParams({ - liquidityDelta: SafeCast.toInt256( - uint256(newLiquidity) - ), + liquidityDelta: SafeCast.toInt256(uint256(newLiquidity)), tickLower: lowerTick, tickUpper: upperTick - }) + }), + new bytes(0) ); + } liquidity = newLiquidity; if (hedgeCommitted0 > 0) { - poolKey.currency0.transfer( - address(poolManager), - hedgeCommitted0 - ); + poolKey.currency0.transfer(address(poolManager), hedgeCommitted0); poolManager.settle(poolKey.currency0); } if (hedgeCommitted1 > 0) { - poolKey.currency1.transfer( - address(poolManager), - hedgeCommitted1 - ); + poolKey.currency1.transfer(address(poolManager), hedgeCommitted1); poolManager.settle(poolKey.currency1); } _mintLeftover(); } else { if (hedgeCommitted0 > 0) { - poolKey.currency0.transfer( - address(poolManager), - hedgeCommitted0 - ); + poolKey.currency0.transfer(address(poolManager), hedgeCommitted0); poolManager.settle(poolKey.currency0); } if (hedgeCommitted1 > 0) { - poolKey.currency1.transfer( - address(poolManager), - hedgeCommitted1 - ); + poolKey.currency1.transfer(address(poolManager), hedgeCommitted1); poolManager.settle(poolKey.currency1); } } @@ -941,85 +762,47 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { } function _mintLeftover() internal { - ( - uint256 currencyBalance0, - uint256 currencyBalance1 - ) = _checkCurrencyBalances(); + (uint256 currencyBalance0, uint256 currencyBalance1) = _checkCurrencyBalances(); if (currencyBalance0 > 0) { - poolManager.mint( - poolKey.currency0, - address(this), - currencyBalance0 - ); + poolManager.mint(poolKey.currency0, address(this), currencyBalance0); } if (currencyBalance1 > 0) { - poolManager.mint( - poolKey.currency1, - address(this), - currencyBalance1 - ); + poolManager.mint(poolKey.currency1, address(this), currencyBalance1); } } function _clear1155Balances() internal { - ( - uint256 currency0Id, - uint256 leftOver0, - uint256 currency1Id, - uint256 leftOver1 - ) = _get1155Balances(); - - if (leftOver0 > 0) - PoolManager(payable(address(poolManager))).safeTransferFrom( - address(this), - address(poolManager), - currency0Id, - leftOver0, - "" + (uint256 leftOver0, uint256 leftOver1) = _get1155Balances(); + + if (leftOver0 > 0) { + PoolManager(payable(address(poolManager))).transfer( + address(poolManager), poolKey.currency0, leftOver0 ); + } - if (leftOver1 > 0) - PoolManager(payable(address(poolManager))).safeTransferFrom( - address(this), - address(poolManager), - currency1Id, - leftOver1, - "" + if (leftOver1 > 0) { + PoolManager(payable(address(poolManager))).transfer( + address(poolManager), poolKey.currency1, leftOver1 ); + } } function _get1155Balances() internal - view - returns ( - uint256 currency0Id, - uint256 leftOver0, - uint256 currency1Id, - uint256 leftOver1 - ) + returns (uint256 leftOver0, uint256 leftOver1) { - currency0Id = CurrencyLibrary.toId(poolKey.currency0); - leftOver0 = poolManager.balanceOf(address(this), currency0Id); - - currency1Id = CurrencyLibrary.toId(poolKey.currency1); - leftOver1 = poolManager.balanceOf(address(this), currency1Id); + leftOver0 = poolManager.balanceOf(address(this), poolKey.currency0); + leftOver1 = poolManager.balanceOf(address(this), poolKey.currency1); } - function _transferFromOrTransferNative( - Currency currency, - address sender, - address target, - uint256 amount - ) internal { + function _transferFromOrTransferNative(Currency currency, address sender, address target, uint256 amount) + internal + { if (currency.isNative()) { _nativeTransfer(target, amount); } else { - ERC20(Currency.unwrap(currency)).safeTransferFrom( - sender, - target, - amount - ); + ERC20(Currency.unwrap(currency)).safeTransferFrom(sender, target, amount); } } @@ -1034,147 +817,94 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { } function _checkCurrencyBalances() internal view returns (uint256, uint256) { - int256 currency0BalanceRaw = poolManager.currencyDelta( - address(this), - poolKey.currency0 - ); + int256 currency0BalanceRaw = poolManager.currencyDelta(address(this), poolKey.currency0); if (currency0BalanceRaw > 0) revert InvalidCurrencyDelta(); uint256 currency0Balance = SafeCast.toUint256(-currency0BalanceRaw); - int256 currency1BalanceRaw = poolManager.currencyDelta( - address(this), - poolKey.currency1 - ); + int256 currency1BalanceRaw = poolManager.currencyDelta(address(this), poolKey.currency1); if (currency1BalanceRaw > 0) revert InvalidCurrencyDelta(); uint256 currency1Balance = SafeCast.toUint256(-currency1BalanceRaw); return (currency0Balance, currency1Balance); } - function _getResetPriceAndLiquidity( - uint160 lastCommittedSqrtPriceX96, - bool isMintOrBurn - ) internal view returns (uint160, uint128) { - ( - uint256 totalHoldings0, - uint256 totalHoldings1 - ) = _checkCurrencyBalances(); + function _getResetPriceAndLiquidity(uint160 lastCommittedSqrtPriceX96, bool isMintOrBurn) + internal + view + returns (uint160, uint128) + { + (uint256 totalHoldings0, uint256 totalHoldings1) = _checkCurrencyBalances(); uint160 sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(lowerTick); uint160 sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(upperTick); uint160 finalSqrtPriceX96; { - (uint256 maxLiquidity0, uint256 maxLiquidity1) = LiquidityAmounts - .getAmountsForLiquidity( - lastCommittedSqrtPriceX96, - sqrtPriceX96Lower, - sqrtPriceX96Upper, - LiquidityAmounts.getLiquidityForAmounts( - lastCommittedSqrtPriceX96, - sqrtPriceX96Lower, - sqrtPriceX96Upper, - totalHoldings0, - totalHoldings1 - ) - ); + (uint256 maxLiquidity0, uint256 maxLiquidity1) = LiquidityAmounts.getAmountsForLiquidity( + lastCommittedSqrtPriceX96, + sqrtPriceX96Lower, + sqrtPriceX96Upper, + LiquidityAmounts.getLiquidityForAmounts( + lastCommittedSqrtPriceX96, sqrtPriceX96Lower, sqrtPriceX96Upper, totalHoldings0, totalHoldings1 + ) + ); /// NOTE one of these should be roughly zero but we don't know which one so we just increase both // (adding 0 or dust to the other side should cause no issue or major imprecision) - uint256 extra0 = FullMath.mulDiv( - totalHoldings0 - maxLiquidity0, - vaultRedepositRate, - _PIPS - ); - uint256 extra1 = FullMath.mulDiv( - totalHoldings1 - maxLiquidity1, - vaultRedepositRate, - _PIPS - ); + uint256 extra0 = FullMath.mulDiv(totalHoldings0 - maxLiquidity0, vaultRedepositRate, _PIPS); + uint256 extra1 = FullMath.mulDiv(totalHoldings1 - maxLiquidity1, vaultRedepositRate, _PIPS); /// NOTE this algorithm only works if liquidity position is full range - uint256 priceX96 = FullMath.mulDiv( - maxLiquidity1 + extra1, - 1 << 96, - maxLiquidity0 + extra0 - ); + uint256 priceX96 = FullMath.mulDiv(maxLiquidity1 + extra1, 1 << 96, maxLiquidity0 + extra0); finalSqrtPriceX96 = SafeCast.toUint160(_sqrt(priceX96) * (1 << 48)); } - if ( - finalSqrtPriceX96 >= sqrtPriceX96Upper || - finalSqrtPriceX96 <= sqrtPriceX96Lower - ) revert PriceOutOfBounds(); + if (finalSqrtPriceX96 >= sqrtPriceX96Upper || finalSqrtPriceX96 <= sqrtPriceX96Lower) revert PriceOutOfBounds(); if (isMintOrBurn) { totalHoldings0 -= 1; totalHoldings1 -= 1; } uint128 finalLiquidity = LiquidityAmounts.getLiquidityForAmounts( - finalSqrtPriceX96, - sqrtPriceX96Lower, - sqrtPriceX96Upper, - totalHoldings0, - totalHoldings1 + finalSqrtPriceX96, sqrtPriceX96Lower, sqrtPriceX96Upper, totalHoldings0, totalHoldings1 ); return (finalSqrtPriceX96, finalLiquidity); } - function _getArbSwap( - ArbSwapParams memory params - ) internal pure returns (uint256 swap0, uint256 swap1) { + function _getArbSwap(ArbSwapParams memory params) internal pure returns (uint256 swap0, uint256 swap1) { /// cannot do arb in zero liquidity if (params.liquidity == 0) revert LiquidityZero(); /// cannot move price to edge of LP positin - if ( - params.newSqrtPriceX96 >= params.sqrtPriceX96Upper || - params.newSqrtPriceX96 <= params.sqrtPriceX96Lower - ) revert PriceOutOfBounds(); + if (params.newSqrtPriceX96 >= params.sqrtPriceX96Upper || params.newSqrtPriceX96 <= params.sqrtPriceX96Lower) { + revert PriceOutOfBounds(); + } /// get amount0/1 of current liquidity - (uint256 current0, uint256 current1) = LiquidityAmounts - .getAmountsForLiquidity( - params.sqrtPriceX96, - params.sqrtPriceX96Lower, - params.sqrtPriceX96Upper, - params.liquidity - ); + (uint256 current0, uint256 current1) = LiquidityAmounts.getAmountsForLiquidity( + params.sqrtPriceX96, params.sqrtPriceX96Lower, params.sqrtPriceX96Upper, params.liquidity + ); /// get amount0/1 of current liquidity if price was newSqrtPriceX96 (uint256 new0, uint256 new1) = LiquidityAmounts.getAmountsForLiquidity( - params.newSqrtPriceX96, - params.sqrtPriceX96Lower, - params.sqrtPriceX96Upper, - params.liquidity + params.newSqrtPriceX96, params.sqrtPriceX96Lower, params.sqrtPriceX96Upper, params.liquidity ); - // question: Is this error necessary? if (new0 == current0 || new1 == current1) revert ArbTooSmall(); bool zeroForOne = new0 > current0; /// differential of info.liquidity amount0/1 at those two prices gives X and Y of classic UniV2 swap /// to get (1-Beta)*X and (1-Beta)*Y for our swap apply `factor` - swap0 = FullMath.mulDiv( - zeroForOne ? new0 - current0 : current0 - new0, - params.betaFactor, - _PIPS - ); - swap1 = FullMath.mulDiv( - zeroForOne ? current1 - new1 : new1 - current1, - params.betaFactor, - _PIPS - ); + swap0 = FullMath.mulDiv(zeroForOne ? new0 - current0 : current0 - new0, params.betaFactor, _PIPS); + swap1 = FullMath.mulDiv(zeroForOne ? current1 - new1 : new1 - current1, params.betaFactor, _PIPS); } function _getBeta(uint256 blockDelta) internal view returns (uint24) { /// if blockDelta = 1 then decay is 0; if blockDelta = 2 then decay is decayRate; if blockDelta = 3 then decay is 2*decayRate etc. uint256 decayAmt = (blockDelta - 1) * decayRate; /// decayAmt downcast is safe here because we know baseBeta < 10000 - uint24 subtractAmt = decayAmt >= baseBeta - ? 0 - : baseBeta - uint24(decayAmt); + uint24 subtractAmt = decayAmt >= baseBeta ? 0 : baseBeta - uint24(decayAmt); return _PIPS - subtractAmt; } @@ -1189,9 +919,7 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { return blockDelta; } - function computeDecPriceFromNewSQRTPrice( - uint160 price - ) internal pure returns (uint256 y) { + function computeDecPriceFromNewSQRTPrice(uint160 price) internal pure returns (uint256 y) { y = FullMath.mulDiv(uint256(price) ** 2, 1, 2 ** 192); } @@ -1203,4 +931,4 @@ contract DiamondHookPoC is BaseHook, ERC20, IERC1155Receiver, ReentrancyGuard { z = (x / z + z) / 2; } } -} \ No newline at end of file +} diff --git a/src/libraries/LiquidityAmounts.sol b/src/libraries/LiquidityAmounts.sol index 4664a01..b9c29c3 100644 --- a/src/libraries/LiquidityAmounts.sol +++ b/src/libraries/LiquidityAmounts.sol @@ -17,26 +17,16 @@ library LiquidityAmounts { /// @param sqrtRatioBX96 Another sqrt price /// @param amount0 The amount0 being sent in /// @return liquidity The amount of returned liquidity - function getLiquidityForAmount0( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint256 amount0 - ) internal pure returns (uint128 liquidity) { - if (sqrtRatioAX96 > sqrtRatioBX96) + function getLiquidityForAmount0(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint256 amount0) + internal + pure + returns (uint128 liquidity) + { + if (sqrtRatioAX96 > sqrtRatioBX96) { (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - uint256 intermediate = FullMath.mulDiv( - sqrtRatioAX96, - sqrtRatioBX96, - FixedPoint96.Q96 - ); - return - toUint128( - FullMath.mulDiv( - amount0, - intermediate, - sqrtRatioBX96 - sqrtRatioAX96 - ) - ); + } + uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96); + return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96)); } /// @notice Computes the amount of liquidity received for a given amount of token1 and price range @@ -45,21 +35,15 @@ library LiquidityAmounts { /// @param sqrtRatioBX96 Another sqrt price /// @param amount1 The amount1 being sent in /// @return liquidity The amount of returned liquidity - function getLiquidityForAmount1( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint256 amount1 - ) internal pure returns (uint128 liquidity) { - if (sqrtRatioAX96 > sqrtRatioBX96) + function getLiquidityForAmount1(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint256 amount1) + internal + pure + returns (uint128 liquidity) + { + if (sqrtRatioAX96 > sqrtRatioBX96) { (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - return - toUint128( - FullMath.mulDiv( - amount1, - FixedPoint96.Q96, - sqrtRatioBX96 - sqrtRatioAX96 - ) - ); + } + return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96)); } /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current @@ -71,34 +55,19 @@ library LiquidityAmounts { uint256 amount0, uint256 amount1 ) internal pure returns (uint128 liquidity) { - if (sqrtRatioAX96 > sqrtRatioBX96) + if (sqrtRatioAX96 > sqrtRatioBX96) { (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + } if (sqrtRatioX96 <= sqrtRatioAX96) { - liquidity = getLiquidityForAmount0( - sqrtRatioAX96, - sqrtRatioBX96, - amount0 - ); + liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0); } else if (sqrtRatioX96 < sqrtRatioBX96) { - uint128 liquidity0 = getLiquidityForAmount0( - sqrtRatioX96, - sqrtRatioBX96, - amount0 - ); - uint128 liquidity1 = getLiquidityForAmount1( - sqrtRatioAX96, - sqrtRatioX96, - amount1 - ); + uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0); + uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1); liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; } else { - liquidity = getLiquidityForAmount1( - sqrtRatioAX96, - sqrtRatioBX96, - amount1 - ); + liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1); } } @@ -107,20 +76,18 @@ library LiquidityAmounts { /// @param sqrtRatioBX96 Another sqrt price /// @param liquidity The liquidity being valued /// @return amount0 The amount0 - function getAmount0ForLiquidity( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint128 liquidity - ) internal pure returns (uint256 amount0) { - if (sqrtRatioAX96 > sqrtRatioBX96) + function getAmount0ForLiquidity(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity) + internal + pure + returns (uint256 amount0) + { + if (sqrtRatioAX96 > sqrtRatioBX96) { (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + } - return - FullMath.mulDiv( - uint256(liquidity) << FixedPoint96.RESOLUTION, - sqrtRatioBX96 - sqrtRatioAX96, - sqrtRatioBX96 - ) / sqrtRatioAX96; + return FullMath.mulDiv( + uint256(liquidity) << FixedPoint96.RESOLUTION, sqrtRatioBX96 - sqrtRatioAX96, sqrtRatioBX96 + ) / sqrtRatioAX96; } /// @notice Computes the amount of token1 for a given amount of liquidity and a price range @@ -128,20 +95,16 @@ library LiquidityAmounts { /// @param sqrtRatioBX96 Another sqrt price /// @param liquidity The liquidity being valued /// @return amount1 The amount1 - function getAmount1ForLiquidity( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint128 liquidity - ) internal pure returns (uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) + function getAmount1ForLiquidity(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity) + internal + pure + returns (uint256 amount1) + { + if (sqrtRatioAX96 > sqrtRatioBX96) { (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + } - return - FullMath.mulDiv( - liquidity, - sqrtRatioBX96 - sqrtRatioAX96, - FixedPoint96.Q96 - ); + return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); } /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current @@ -152,33 +115,17 @@ library LiquidityAmounts { uint160 sqrtRatioBX96, uint128 liquidity ) internal pure returns (uint256 amount0, uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) + if (sqrtRatioAX96 > sqrtRatioBX96) { (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + } if (sqrtRatioX96 <= sqrtRatioAX96) { - amount0 = getAmount0ForLiquidity( - sqrtRatioAX96, - sqrtRatioBX96, - liquidity - ); + amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); } else if (sqrtRatioX96 < sqrtRatioBX96) { - amount0 = getAmount0ForLiquidity( - sqrtRatioX96, - sqrtRatioBX96, - liquidity - ); - amount1 = getAmount1ForLiquidity( - sqrtRatioAX96, - sqrtRatioX96, - liquidity - ); - + amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity); + amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity); } else { - amount1 = getAmount1ForLiquidity( - sqrtRatioAX96, - sqrtRatioBX96, - liquidity - ); + amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); } } -} \ No newline at end of file +} diff --git a/test/DiamondHook.t.sol b/test/DiamondHook.t.sol index 197cc7e..4305277 100644 --- a/test/DiamondHook.t.sol +++ b/test/DiamondHook.t.sol @@ -1,436 +1,436 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {PoolKey, PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; -import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; -import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; -import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; -import {LiquidityAmounts} from "../src/libraries/LiquidityAmounts.sol"; -import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; -import {console} from "forge-std/console.sol"; - -import {DiamondHookPoC} from "../src/DiamondHookFutures.sol"; -import {DiamondHookImpl} from "./utils/DiamondHookImpl.sol"; - -contract TestDiamondHook is Test, Deployers, GasSnapshot { - using PoolIdLibrary for PoolKey; - using CurrencyLibrary for Currency; - - PoolManager manager; - address hookAddress; - TestERC20 token0; - TestERC20 token1; - PoolId poolId; - DiamondHookPoC hook = DiamondHookPoC(address( - uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG |Hooks.AFTER_SWAP_FLAG - ) - )); - PoolKey poolKey; - - PoolSwapTest swapRouter; - - uint24 constant PIPS = 1000000; - int24 public tickSpacing = 10; - uint24 public baseBeta = PIPS/2; // % expressed as uint < 1e6 - uint24 public decayRate = PIPS/10; // % expressed as uint < 1e6 - uint24 public vaultRedepositRate = PIPS/10; // % expressed as uint < 1e6 - // we also want to pass in a minimum constant amount (maybe even a % of total pool size, so the vault eventually empties) - // if we only ever take 1% of the vault, the vault may never empty. - uint24 public fee = 1000; // % expressed as uint < 1e6 - - int24 public lowerTick; - int24 public upperTick; - - function setUp() public { - token0 = new TestERC20(2**128); - token1 = new TestERC20(2**128); - - if (uint160(address(token0)) > uint160(address(token1))) { - TestERC20 temp = token0; - token0 = token1; - token1 = temp; - } - - manager = new PoolManager(500000); - - DiamondHookImpl impl = new DiamondHookImpl(manager, hook,tickSpacing,baseBeta,decayRate,vaultRedepositRate); - (, bytes32[] memory writes) = vm.accesses(address(impl)); - vm.etch(address(hook), address(impl).code); - // for each storage key that was written during the hook implementation, copy the value over - unchecked { - for (uint256 i = 0; i < writes.length; i++) { - bytes32 slot = writes[i]; - vm.store(address(hook), slot, vm.load(address(impl), slot)); - } - } - // Create the pool - poolKey = PoolKey( - Currency.wrap(address(token0)), - Currency.wrap(address(token1)), - fee, - tickSpacing, - hook - ); - poolId = poolKey.toId(); - uint256 price = 1; - manager.initialize(poolKey, computeNewSQRTPrice(price)); - swapRouter = new PoolSwapTest(manager); - token0.approve(address(hook), type(uint256).max); - token1.approve(address(hook), type(uint256).max); - token0.approve(address(swapRouter), type(uint256).max); - token1.approve(address(swapRouter), type(uint256).max); - lowerTick = hook.lowerTick(); - upperTick = hook.upperTick(); - - assertEq(lowerTick, -887270); - assertEq(upperTick, 887270); - } - - function testOpeningTotalSupplyZero() public { - uint256 height = 1; - vm.roll(height); - - // do arb swap (price rises from initial) - uint160 newSQRTPrice = computeNewSQRTPrice(4); - - vm.expectRevert( - abi.encodeWithSelector( - DiamondHookPoC.TotalSupplyZero.selector - ) - ); - - hook.openPool(newSQRTPrice); - } - - function testBasicArbSwap() public { - // mint some liquidity - hook.mint(10**18,address(this)); - uint256 height = 1; - vm.roll(height); - - // get starting values - uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); - uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); - uint128 liquidityBefore = manager.getLiquidity(poolId); - uint256 balance0ThisBefore = token0.balanceOf(address(this)); - uint256 balance1ThisBefore = token1.balanceOf(address(this)); - - // do arb swap (price rises from initial) - uint160 newSQRTPrice = computeNewSQRTPrice(4); - hook.openPool(newSQRTPrice); - - // get ending values - (uint160 newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); - uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); - uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); - uint128 liquidityAfter = manager.getLiquidity(poolId); - uint256 balance0ThisAfter = token0.balanceOf(address(this)); - uint256 balance1ThisAfter = token1.balanceOf(address(this)); - - // check expectations - assertEq(newSQRTPriceCheck, newSQRTPrice); // pool price moved to where arb swap specified - assertGt(balance1ManagerAfter, balance1ManagerBefore); // pool gained token0 - assertGt(balance0ManagerBefore, balance0ManagerAfter); // pool lost token1 - assertGt(balance1ThisBefore, balance1ThisAfter); // arber lost token0 - assertGt(balance0ThisAfter, balance0ThisBefore); // arber gained token1 - assertEq(balance0ThisAfter-balance0ThisBefore, balance0ManagerBefore-balance0ManagerAfter); // net is 0 - assertEq(balance1ThisBefore-balance1ThisAfter, balance1ManagerAfter-balance1ManagerBefore); // net is 0 - assertGt(liquidityBefore, liquidityAfter); // liquidity decreased - - // reset starting values - balance0ThisBefore = balance0ThisAfter; - balance1ThisBefore = balance1ThisAfter; - balance0ManagerBefore = balance0ManagerAfter; - balance1ManagerBefore = balance1ManagerAfter; - - // go to next block - vm.roll(height+1); - - // do arb swap (price back down to initial) - newSQRTPrice = computeNewSQRTPrice(1); - hook.openPool(newSQRTPrice); - - // get ending values - balance0ThisAfter = token0.balanceOf(address(this)); - balance1ThisAfter = token1.balanceOf(address(this)); - balance0ManagerAfter = token0.balanceOf(address(manager)); - balance1ManagerAfter = token1.balanceOf(address(manager)); - (newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); - - // check expectations - assertEq(newSQRTPrice, newSQRTPriceCheck); - assertGt(balance1ManagerBefore, balance1ManagerAfter); - assertGt(balance0ManagerAfter, balance0ManagerBefore); - assertGt(balance1ThisAfter, balance1ThisBefore); - assertGt(balance0ThisBefore, balance0ThisAfter); - assertEq(balance1ThisAfter-balance1ThisBefore, balance1ManagerBefore-balance1ManagerAfter); - assertEq(balance0ThisBefore-balance0ThisAfter, balance0ManagerAfter-balance0ManagerBefore); - - uint160 liquidityAfter2 = manager.getLiquidity(poolId); - assertGt(liquidityAfter2, liquidityAfter); // liquidity actually increased (price moved back, can redeposit more vault) - assertGt(liquidityBefore, liquidityAfter2); // but liquidity still less than originally - } - - function testManyWhipSaws() public { - hook.mint(10**18,address(this)); - uint256 height = 1; - uint256 price; - uint160 newSQRTPrice; - - uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); - uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); - (uint256 liquidity0Before, uint256 liquidity1Before) = getTokenReservesInPool(); - - assertEq(balance0ManagerBefore-1, liquidity0Before); - assertEq(balance1ManagerBefore-1, liquidity1Before); - - for(uint256 i = 0; i < 5; i++) { - vm.roll(height++); - price = 4; - newSQRTPrice=computeNewSQRTPrice(price); - hook.openPool(newSQRTPrice); - vm.roll(height++); - price = 1; - newSQRTPrice=computeNewSQRTPrice(price); - hook.openPool(newSQRTPrice); - } - - uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); - uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); - (uint256 liquidity0After, uint256 liquidity1After) = getTokenReservesInPool(); - - assertGt(liquidity0Before, liquidity0After); - assertGt(liquidity1Before, liquidity1After); - - uint256 undeposited0 = balance0ManagerAfter - liquidity0After; - uint256 undeposited1 = balance1ManagerAfter - liquidity1After; - uint256 dustThreshold = 100; - - // should still have deposited almost ALL of of one or the other token (modulo some dust) - assertGt(dustThreshold, undeposited0 > undeposited1 ? undeposited1 : undeposited0); - } - - function testWithdraw() public { - hook.mint(10**18,address(this)); - uint256 totalSupply1 = hook.totalSupply(); - assertEq(totalSupply1, 10**18); - - uint256 height = 1; - uint256 price = 4; - uint160 newSQRTPrice = computeNewSQRTPrice(price); - hook.openPool(newSQRTPrice); - - hook.burn(10**16, address(this)); - - uint256 totalSupply2 = hook.totalSupply(); - assertEq(totalSupply2, totalSupply1-10**16); - - hook.burn(totalSupply2, address(this)); - assertEq(hook.totalSupply(), 0); - uint256 balance0Manager = token0.balanceOf(address(manager)); - uint256 balance1Manager = token1.balanceOf(address(manager)); - - // console.log(balance0Manager); - // console.log(balance1Manager); - - // this is kind of high IMO we are already somehow losing 3 wei in both tokens - // seems like we may somehow losing track of 1 wei into the contract - // not just for every openPool() but on other ops too? - uint256 dustThreshold = 4; - assertGt(dustThreshold, balance0Manager); - assertGt(dustThreshold, balance1Manager); - - hook.mint(10**18, address(this)); - - vm.roll(++height); - price = 2; - newSQRTPrice=computeNewSQRTPrice(price); - hook.openPool(newSQRTPrice); - - // test mint/burn invariant (you get back as much as you put in if nothing else changes (no swaps etc) - uint256 balance0Before = token0.balanceOf(address(this)); - uint256 balance1Before = token1.balanceOf(address(this)); - - //console.log("midway balances:",token0.balanceOf(address(manager)),token1.balanceOf(address(manager))); - hook.mint(10**18, address(this)); - hook.burn(10**18, address(this)); - hook.mint(10**24, address(this)); - hook.burn(10**24, address(this)); - - // NOTE this invariant is not working amounts are slightly off!!!! - //assertEq(token0.balanceOf(address(this)), balance0Before); - //assertEq(token1.balanceOf(address(this)), balance1Before); - - vm.roll(++height); - price = 4; - newSQRTPrice=computeNewSQRTPrice(price); - hook.openPool(newSQRTPrice); - - hook.burn(10**10, address(this)); - vm.roll(++height); - balance0Before = token0.balanceOf(address(this)); - balance1Before = token1.balanceOf(address(this)); - //console.log("Let's try a TOB mint"); - // (uint160 poolPrice,,,,,)=manager.getSlot0(poolId); - // (uint256 x, uint256 y)=getTokenReservesInPool(); - // console.log("new block, pre-mint. reserves:",x,y,poolPrice); - // console.log("new block, pre-mint. pool-price:",poolPrice); - - hook.mint(10**12, address(this)); - // (poolPrice,,,,,)=manager.getSlot0(poolId); - // (x, y)=getTokenReservesInPool(); - // console.log("new block, post-mint. reserves:",x,y,poolPrice); - // console.log("new block, post-mint. pool-price:",poolPrice); - hook.openPool(newSQRTPrice); - //console.log("before and after difference token 0:",token0.balanceOf(address(this))-balance0Before); - //console.log("before and after difference token 1:",token1.balanceOf(address(this))-balance1Before); - - vm.roll(++height); - // (poolPrice,,,,,)=manager.getSlot0(poolId); - // (x, y)=getTokenReservesInPool(); - // console.log("new block, pre-burn. reserves:",x,y,poolPrice); - // console.log("new block, pre-burn. pool-price:",poolPrice); - hook.burn(10**12, address(this)); - // (poolPrice,,,,,)=manager.getSlot0(poolId); - // (x, y)=getTokenReservesInPool(); - // console.log("new block, post-burn. reserves:",x,y,poolPrice); - // console.log("new block, post-burn. pool-price:",poolPrice); - hook.openPool(newSQRTPrice); - - hook.burn(hook.totalSupply(), address(this)); - assertEq(hook.totalSupply(), 0); - - - balance0Manager = token0.balanceOf(address(manager)); - balance1Manager = token1.balanceOf(address(manager)); - dustThreshold = 18; - assertGt(dustThreshold, balance0Manager); - assertGt(dustThreshold, balance1Manager); - } - - function testSwaps() public { - hook.mint(10**20,address(this)); - uint256 height = 1; - uint256 price = 4; - uint160 newSQRTPrice = computeNewSQRTPrice(price); - - hook.openPool(newSQRTPrice); - uint128 hedgeCommit0=10**18; - uint128 hedgeCommit1=10**18; - - // must deposit hedge tokens before swap can take place - hook.depositHedgeCommitment(hedgeCommit0, hedgeCommit1); - - // prepare swap token0 for token1 - IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10**15, sqrtPriceLimitX96:computeNewSQRTPrice(3)}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - - uint256 balance0Before = token0.balanceOf(address(this)); - uint256 balance1Before = token1.balanceOf(address(this)); - - swapRouter.swap(poolKey, params, settings); - - uint256 balance0After = token0.balanceOf(address(this)); - uint256 balance1After = token1.balanceOf(address(this)); - - uint256 hedgeRequired0 = hook.hedgeRequired0(); - uint256 hedgeRequired1 = hook.hedgeRequired1(); - - assertGt(balance0Before, balance0After); - assertGt(balance1After, balance1Before); - assertGt(hedgeRequired1, 0); - assertEq(hedgeRequired0, 0); - assertEq(balance1After-balance1Before, hedgeRequired1); - - // now the swapper is going to sell token 1s for 0s back to the pool - // in approx the same size as before - // to move the pool price back to original price - params = IPoolManager.SwapParams({zeroForOne: false, amountSpecified: -int256(hedgeRequired1), sqrtPriceLimitX96:computeNewSQRTPrice(5)}); - - balance0Before = token0.balanceOf(address(this)); - balance1Before = token1.balanceOf(address(this)); - - swapRouter.swap(poolKey, params, settings); - - balance0After = token0.balanceOf(address(this)); - balance1After = token1.balanceOf(address(this)); - - hedgeRequired0 = hook.hedgeRequired0(); - hedgeRequired1 = hook.hedgeRequired1(); - - assertGt(balance1Before, balance1After); - assertGt(balance0After, balance0Before); - assertGt(hedgeRequired0, 0); - assertEq(hedgeRequired1, 0); - - hook.withdrawHedgeCommitment(hedgeCommit0-hedgeRequired0, hedgeCommit1); - - params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 2**80, sqrtPriceLimitX96:computeNewSQRTPrice(4)}); - - balance0Before = token0.balanceOf(address(this)); - balance1Before = token1.balanceOf(address(this)); - - swapRouter.swap(poolKey, params, settings); - - balance0After = token0.balanceOf(address(this)); - balance1After = token1.balanceOf(address(this)); - - hedgeRequired0 = hook.hedgeRequired0(); - hedgeRequired1 = hook.hedgeRequired1(); - - assertGt(balance0Before, balance0After); - assertGt(balance1After, balance1Before); - assertEq(hedgeRequired0, 0); - assertEq(hedgeRequired1, 0); - - hook.withdrawHedgeCommitment(hook.hedgeCommitted0(), 0); - - assertEq(hook.hedgeCommitted0(), 0); - assertEq(hook.hedgeCommitted1(), 0); - - vm.roll(++height); - price=2; - newSQRTPrice=computeNewSQRTPrice(price); - hook.openPool(newSQRTPrice); - } - - function _sqrt(uint256 x) internal pure returns (uint256 y) { - uint256 z = (x + 1) / 2; - y = x; - while (z < y) { - y = z; - z = (x / z + z) / 2; - } - } - - function computeNewSQRTPrice(uint256 price) internal pure returns (uint160 y){ - y=uint160(_sqrt(price*2**96)*2**48); - } - - function getTokenReservesInPool() public view returns (uint256 x, uint256 y){ - Position.Info memory info = manager.getPosition(poolId, address(hook), lowerTick, upperTick); - (uint160 poolPrice,,,,,)= manager.getSlot0(poolId); - (x, y) = LiquidityAmounts.getAmountsForLiquidity( - poolPrice, - TickMath.getSqrtRatioAtTick(lowerTick), - TickMath.getSqrtRatioAtTick(upperTick), - info.liquidity - ); - } -} \ No newline at end of file +// // SPDX-License-Identifier: MIT +// pragma solidity ^0.8.20; + +// import "forge-std/Test.sol"; +// import {Vm} from "forge-std/Vm.sol"; +// import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +// import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +// import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; +// import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +// import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +// import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +// import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +// import {PoolKey, PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +// import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +// import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +// import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; +// import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +// import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +// import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +// import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +// import {LiquidityAmounts} from "../src/libraries/LiquidityAmounts.sol"; +// import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +// import {console} from "forge-std/console.sol"; + +// import {DiamondHookFutures} from "../src/DiamondHookFutures.sol"; +// import {DiamondHookImpl} from "./utils/DiamondHookImpl.sol"; + +// contract TestDiamondHook is Test, Deployers, GasSnapshot { +// using PoolIdLibrary for PoolKey; +// using CurrencyLibrary for Currency; + +// PoolManager manager; +// address hookAddress; +// TestERC20 token0; +// TestERC20 token1; +// PoolId poolId; +// DiamondHookFutures hook = DiamondHookFutures( +// address( +// uint160( +// Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG +// | Hooks.AFTER_SWAP_FLAG +// ) +// ) +// ); +// PoolKey poolKey; + +// PoolSwapTest swapRouter; + +// uint24 constant PIPS = 1000000; +// int24 public tickSpacing = 10; +// uint24 public baseBeta = PIPS / 2; // % expressed as uint < 1e6 +// uint24 public decayRate = PIPS / 10; // % expressed as uint < 1e6 +// uint24 public vaultRedepositRate = PIPS / 10; // % expressed as uint < 1e6 +// // we also want to pass in a minimum constant amount (maybe even a % of total pool size, so the vault eventually empties) +// // if we only ever take 1% of the vault, the vault may never empty. +// uint24 public fee = 1000; // % expressed as uint < 1e6 + +// int24 public lowerTick; +// int24 public upperTick; + +// function setUp() public { +// token0 = new TestERC20(2 ** 128); +// token1 = new TestERC20(2 ** 128); + +// if (uint160(address(token0)) > uint160(address(token1))) { +// TestERC20 temp = token0; +// token0 = token1; +// token1 = temp; +// } + +// manager = new PoolManager(500000); + +// DiamondHookImpl impl = new DiamondHookImpl(manager, hook, tickSpacing, baseBeta, decayRate, vaultRedepositRate); +// (, bytes32[] memory writes) = vm.accesses(address(impl)); +// vm.etch(address(hook), address(impl).code); +// // for each storage key that was written during the hook implementation, copy the value over +// unchecked { +// for (uint256 i = 0; i < writes.length; i++) { +// bytes32 slot = writes[i]; +// vm.store(address(hook), slot, vm.load(address(impl), slot)); +// } +// } +// // Create the pool +// poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), fee, tickSpacing, hook); +// poolId = poolKey.toId(); +// uint256 price = 1; +// manager.initialize(poolKey, computeNewSQRTPrice(price)); +// swapRouter = new PoolSwapTest(manager); +// token0.approve(address(hook), type(uint256).max); +// token1.approve(address(hook), type(uint256).max); +// token0.approve(address(swapRouter), type(uint256).max); +// token1.approve(address(swapRouter), type(uint256).max); +// lowerTick = hook.lowerTick(); +// upperTick = hook.upperTick(); + +// assertEq(lowerTick, -887270); +// assertEq(upperTick, 887270); +// } + +// function testOpeningTotalSupplyZero() public { +// uint256 height = 1; +// vm.roll(height); + +// // do arb swap (price rises from initial) +// uint160 newSQRTPrice = computeNewSQRTPrice(4); + +// vm.expectRevert(abi.encodeWithSelector(DiamondHookFutures.TotalSupplyZero.selector)); + +// hook.openPool(newSQRTPrice); +// } + +// function testBasicArbSwap() public { +// // mint some liquidity +// hook.mint(10 ** 18, address(this)); +// uint256 height = 1; +// vm.roll(height); + +// // get starting values +// uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); +// uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); +// uint128 liquidityBefore = manager.getLiquidity(poolId); +// uint256 balance0ThisBefore = token0.balanceOf(address(this)); +// uint256 balance1ThisBefore = token1.balanceOf(address(this)); + +// // do arb swap (price rises from initial) +// uint160 newSQRTPrice = computeNewSQRTPrice(4); +// hook.openPool(newSQRTPrice); + +// // get ending values +// (uint160 newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); +// uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); +// uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); +// uint128 liquidityAfter = manager.getLiquidity(poolId); +// uint256 balance0ThisAfter = token0.balanceOf(address(this)); +// uint256 balance1ThisAfter = token1.balanceOf(address(this)); + +// // check expectations +// assertEq(newSQRTPriceCheck, newSQRTPrice); // pool price moved to where arb swap specified +// assertGt(balance1ManagerAfter, balance1ManagerBefore); // pool gained token0 +// assertGt(balance0ManagerBefore, balance0ManagerAfter); // pool lost token1 +// assertGt(balance1ThisBefore, balance1ThisAfter); // arber lost token0 +// assertGt(balance0ThisAfter, balance0ThisBefore); // arber gained token1 +// assertEq(balance0ThisAfter - balance0ThisBefore, balance0ManagerBefore - balance0ManagerAfter); // net is 0 +// assertEq(balance1ThisBefore - balance1ThisAfter, balance1ManagerAfter - balance1ManagerBefore); // net is 0 +// assertGt(liquidityBefore, liquidityAfter); // liquidity decreased + +// // reset starting values +// balance0ThisBefore = balance0ThisAfter; +// balance1ThisBefore = balance1ThisAfter; +// balance0ManagerBefore = balance0ManagerAfter; +// balance1ManagerBefore = balance1ManagerAfter; + +// // go to next block +// vm.roll(height + 1); + +// // do arb swap (price back down to initial) +// newSQRTPrice = computeNewSQRTPrice(1); +// hook.openPool(newSQRTPrice); + +// // get ending values +// balance0ThisAfter = token0.balanceOf(address(this)); +// balance1ThisAfter = token1.balanceOf(address(this)); +// balance0ManagerAfter = token0.balanceOf(address(manager)); +// balance1ManagerAfter = token1.balanceOf(address(manager)); +// (newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); + +// // check expectations +// assertEq(newSQRTPrice, newSQRTPriceCheck); +// assertGt(balance1ManagerBefore, balance1ManagerAfter); +// assertGt(balance0ManagerAfter, balance0ManagerBefore); +// assertGt(balance1ThisAfter, balance1ThisBefore); +// assertGt(balance0ThisBefore, balance0ThisAfter); +// assertEq(balance1ThisAfter - balance1ThisBefore, balance1ManagerBefore - balance1ManagerAfter); +// assertEq(balance0ThisBefore - balance0ThisAfter, balance0ManagerAfter - balance0ManagerBefore); + +// uint160 liquidityAfter2 = manager.getLiquidity(poolId); +// assertGt(liquidityAfter2, liquidityAfter); // liquidity actually increased (price moved back, can redeposit more vault) +// assertGt(liquidityBefore, liquidityAfter2); // but liquidity still less than originally +// } + +// function testManyWhipSaws() public { +// hook.mint(10 ** 18, address(this)); +// uint256 height = 1; +// uint256 price; +// uint160 newSQRTPrice; + +// uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); +// uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); +// (uint256 liquidity0Before, uint256 liquidity1Before) = getTokenReservesInPool(); + +// assertEq(balance0ManagerBefore - 1, liquidity0Before); +// assertEq(balance1ManagerBefore - 1, liquidity1Before); + +// for (uint256 i = 0; i < 5; i++) { +// vm.roll(height++); +// price = 4; +// newSQRTPrice = computeNewSQRTPrice(price); +// hook.openPool(newSQRTPrice); +// vm.roll(height++); +// price = 1; +// newSQRTPrice = computeNewSQRTPrice(price); +// hook.openPool(newSQRTPrice); +// } + +// uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); +// uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); +// (uint256 liquidity0After, uint256 liquidity1After) = getTokenReservesInPool(); + +// assertGt(liquidity0Before, liquidity0After); +// assertGt(liquidity1Before, liquidity1After); + +// uint256 undeposited0 = balance0ManagerAfter - liquidity0After; +// uint256 undeposited1 = balance1ManagerAfter - liquidity1After; +// uint256 dustThreshold = 100; + +// // should still have deposited almost ALL of of one or the other token (modulo some dust) +// assertGt(dustThreshold, undeposited0 > undeposited1 ? undeposited1 : undeposited0); +// } + +// function testWithdraw() public { +// hook.mint(10 ** 18, address(this)); +// uint256 totalSupply1 = hook.totalSupply(); +// assertEq(totalSupply1, 10 ** 18); + +// uint256 height = 1; +// uint256 price = 4; +// uint160 newSQRTPrice = computeNewSQRTPrice(price); +// hook.openPool(newSQRTPrice); + +// hook.burn(10 ** 16, address(this)); + +// uint256 totalSupply2 = hook.totalSupply(); +// assertEq(totalSupply2, totalSupply1 - 10 ** 16); + +// hook.burn(totalSupply2, address(this)); +// assertEq(hook.totalSupply(), 0); +// uint256 balance0Manager = token0.balanceOf(address(manager)); +// uint256 balance1Manager = token1.balanceOf(address(manager)); + +// // console.log(balance0Manager); +// // console.log(balance1Manager); + +// // this is kind of high IMO we are already somehow losing 3 wei in both tokens +// // seems like we may somehow losing track of 1 wei into the contract +// // not just for every openPool() but on other ops too? +// uint256 dustThreshold = 4; +// assertGt(dustThreshold, balance0Manager); +// assertGt(dustThreshold, balance1Manager); + +// hook.mint(10 ** 18, address(this)); + +// vm.roll(++height); +// price = 2; +// newSQRTPrice = computeNewSQRTPrice(price); +// hook.openPool(newSQRTPrice); + +// // test mint/burn invariant (you get back as much as you put in if nothing else changes (no swaps etc) +// uint256 balance0Before = token0.balanceOf(address(this)); +// uint256 balance1Before = token1.balanceOf(address(this)); + +// //console.log("midway balances:",token0.balanceOf(address(manager)),token1.balanceOf(address(manager))); +// hook.mint(10 ** 18, address(this)); +// hook.burn(10 ** 18, address(this)); +// hook.mint(10 ** 24, address(this)); +// hook.burn(10 ** 24, address(this)); + +// // NOTE this invariant is not working amounts are slightly off!!!! +// //assertEq(token0.balanceOf(address(this)), balance0Before); +// //assertEq(token1.balanceOf(address(this)), balance1Before); + +// vm.roll(++height); +// price = 4; +// newSQRTPrice = computeNewSQRTPrice(price); +// hook.openPool(newSQRTPrice); + +// hook.burn(10 ** 10, address(this)); +// vm.roll(++height); +// balance0Before = token0.balanceOf(address(this)); +// balance1Before = token1.balanceOf(address(this)); +// //console.log("Let's try a TOB mint"); +// // (uint160 poolPrice,,,,,)=manager.getSlot0(poolId); +// // (uint256 x, uint256 y)=getTokenReservesInPool(); +// // console.log("new block, pre-mint. reserves:",x,y,poolPrice); +// // console.log("new block, pre-mint. pool-price:",poolPrice); + +// hook.mint(10 ** 12, address(this)); +// // (poolPrice,,,,,)=manager.getSlot0(poolId); +// // (x, y)=getTokenReservesInPool(); +// // console.log("new block, post-mint. reserves:",x,y,poolPrice); +// // console.log("new block, post-mint. pool-price:",poolPrice); +// hook.openPool(newSQRTPrice); +// //console.log("before and after difference token 0:",token0.balanceOf(address(this))-balance0Before); +// //console.log("before and after difference token 1:",token1.balanceOf(address(this))-balance1Before); + +// vm.roll(++height); +// // (poolPrice,,,,,)=manager.getSlot0(poolId); +// // (x, y)=getTokenReservesInPool(); +// // console.log("new block, pre-burn. reserves:",x,y,poolPrice); +// // console.log("new block, pre-burn. pool-price:",poolPrice); +// hook.burn(10 ** 12, address(this)); +// // (poolPrice,,,,,)=manager.getSlot0(poolId); +// // (x, y)=getTokenReservesInPool(); +// // console.log("new block, post-burn. reserves:",x,y,poolPrice); +// // console.log("new block, post-burn. pool-price:",poolPrice); +// hook.openPool(newSQRTPrice); + +// hook.burn(hook.totalSupply(), address(this)); +// assertEq(hook.totalSupply(), 0); + +// balance0Manager = token0.balanceOf(address(manager)); +// balance1Manager = token1.balanceOf(address(manager)); +// dustThreshold = 18; +// assertGt(dustThreshold, balance0Manager); +// assertGt(dustThreshold, balance1Manager); +// } + +// function testSwaps() public { +// hook.mint(10 ** 20, address(this)); +// uint256 height = 1; +// uint256 price = 4; +// uint160 newSQRTPrice = computeNewSQRTPrice(price); + +// hook.openPool(newSQRTPrice); +// uint128 hedgeCommit0 = 10 ** 18; +// uint128 hedgeCommit1 = 10 ** 18; + +// // must deposit hedge tokens before swap can take place +// hook.depositHedgeCommitment(hedgeCommit0, hedgeCommit1); + +// // prepare swap token0 for token1 +// IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ +// zeroForOne: true, +// amountSpecified: 10 ** 15, +// sqrtPriceLimitX96: computeNewSQRTPrice(3) +// }); +// PoolSwapTest.TestSettings memory settings = +// PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + +// uint256 balance0Before = token0.balanceOf(address(this)); +// uint256 balance1Before = token1.balanceOf(address(this)); + +// swapRouter.swap(poolKey, params, settings); + +// uint256 balance0After = token0.balanceOf(address(this)); +// uint256 balance1After = token1.balanceOf(address(this)); + +// uint256 hedgeRequired0 = hook.hedgeRequired0(); +// uint256 hedgeRequired1 = hook.hedgeRequired1(); + +// assertGt(balance0Before, balance0After); +// assertGt(balance1After, balance1Before); +// assertGt(hedgeRequired1, 0); +// assertEq(hedgeRequired0, 0); +// assertEq(balance1After - balance1Before, hedgeRequired1); + +// // now the swapper is going to sell token 1s for 0s back to the pool +// // in approx the same size as before +// // to move the pool price back to original price +// params = IPoolManager.SwapParams({ +// zeroForOne: false, +// amountSpecified: -int256(hedgeRequired1), +// sqrtPriceLimitX96: computeNewSQRTPrice(5) +// }); + +// balance0Before = token0.balanceOf(address(this)); +// balance1Before = token1.balanceOf(address(this)); + +// swapRouter.swap(poolKey, params, settings); + +// balance0After = token0.balanceOf(address(this)); +// balance1After = token1.balanceOf(address(this)); + +// hedgeRequired0 = hook.hedgeRequired0(); +// hedgeRequired1 = hook.hedgeRequired1(); + +// assertGt(balance1Before, balance1After); +// assertGt(balance0After, balance0Before); +// assertGt(hedgeRequired0, 0); +// assertEq(hedgeRequired1, 0); + +// hook.withdrawHedgeCommitment(hedgeCommit0 - hedgeRequired0, hedgeCommit1); + +// params = IPoolManager.SwapParams({ +// zeroForOne: true, +// amountSpecified: 2 ** 80, +// sqrtPriceLimitX96: computeNewSQRTPrice(4) +// }); + +// balance0Before = token0.balanceOf(address(this)); +// balance1Before = token1.balanceOf(address(this)); + +// swapRouter.swap(poolKey, params, settings); + +// balance0After = token0.balanceOf(address(this)); +// balance1After = token1.balanceOf(address(this)); + +// hedgeRequired0 = hook.hedgeRequired0(); +// hedgeRequired1 = hook.hedgeRequired1(); + +// assertGt(balance0Before, balance0After); +// assertGt(balance1After, balance1Before); +// assertEq(hedgeRequired0, 0); +// assertEq(hedgeRequired1, 0); + +// hook.withdrawHedgeCommitment(hook.hedgeCommitted0(), 0); + +// assertEq(hook.hedgeCommitted0(), 0); +// assertEq(hook.hedgeCommitted1(), 0); + +// vm.roll(++height); +// price = 2; +// newSQRTPrice = computeNewSQRTPrice(price); +// hook.openPool(newSQRTPrice); +// } + +// function _sqrt(uint256 x) internal pure returns (uint256 y) { +// uint256 z = (x + 1) / 2; +// y = x; +// while (z < y) { +// y = z; +// z = (x / z + z) / 2; +// } +// } + +// function computeNewSQRTPrice(uint256 price) internal pure returns (uint160 y) { +// y = uint160(_sqrt(price * 2 ** 96) * 2 ** 48); +// } + +// function getTokenReservesInPool() public view returns (uint256 x, uint256 y) { +// Position.Info memory info = manager.getPosition(poolId, address(hook), lowerTick, upperTick); +// (uint160 poolPrice,,,,,) = manager.getSlot0(poolId); +// (x, y) = LiquidityAmounts.getAmountsForLiquidity( +// poolPrice, TickMath.getSqrtRatioAtTick(lowerTick), TickMath.getSqrtRatioAtTick(upperTick), info.liquidity +// ); +// } +// } diff --git a/test/utils/DiamondHookImpl.sol b/test/utils/DiamondHookImpl.sol index 619606e..a1849bd 100644 --- a/test/utils/DiamondHookImpl.sol +++ b/test/utils/DiamondHookImpl.sol @@ -1,16 +1,23 @@ pragma solidity ^0.8.20; -import {DiamondHookPoC} from "../../src/DiamondHookFutures.sol"; +import {DiamondHookFutures} from "../../src/DiamondHookFutures.sol"; import {BaseHook} from "@uniswap/v4-periphery/contracts/BaseHook.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -contract DiamondHookImpl is DiamondHookPoC{ - constructor(IPoolManager poolManager, DiamondHookPoC addressToEtch, int24 tickSpacing, uint24 baseBeta, uint24 decayRate, uint24 vaultRedepositRate) DiamondHookPoC(poolManager,tickSpacing,baseBeta,decayRate,vaultRedepositRate) { +contract DiamondHookImpl is DiamondHookFutures { + constructor( + IPoolManager poolManager, + DiamondHookFutures addressToEtch, + int24 tickSpacing, + uint24 baseBeta, + uint24 decayRate, + uint24 vaultRedepositRate + ) DiamondHookFutures(poolManager, tickSpacing, baseBeta, decayRate, vaultRedepositRate) { Hooks.validateHookAddress(addressToEtch, getHooksCalls()); } // make this a no-op in testing function validateHookAddress(BaseHook _this) internal pure override {} -} \ No newline at end of file +} From 887c9939059aa55adbf8a437a11fad9b46afea4a Mon Sep 17 00:00:00 2001 From: sm-stack Date: Thu, 4 Jan 2024 03:47:34 +0900 Subject: [PATCH 6/6] Fix incompatibilities in test --- test/DiamondHook.t.sol | 872 ++++++++++++++++++++--------------------- 1 file changed, 436 insertions(+), 436 deletions(-) diff --git a/test/DiamondHook.t.sol b/test/DiamondHook.t.sol index 4305277..280de9e 100644 --- a/test/DiamondHook.t.sol +++ b/test/DiamondHook.t.sol @@ -1,436 +1,436 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.20; - -// import "forge-std/Test.sol"; -// import {Vm} from "forge-std/Vm.sol"; -// import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -// import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -// import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; -// import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -// import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -// import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -// import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -// import {PoolKey, PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -// import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -// import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -// import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; -// import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; -// import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; -// import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; -// import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; -// import {LiquidityAmounts} from "../src/libraries/LiquidityAmounts.sol"; -// import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; -// import {console} from "forge-std/console.sol"; - -// import {DiamondHookFutures} from "../src/DiamondHookFutures.sol"; -// import {DiamondHookImpl} from "./utils/DiamondHookImpl.sol"; - -// contract TestDiamondHook is Test, Deployers, GasSnapshot { -// using PoolIdLibrary for PoolKey; -// using CurrencyLibrary for Currency; - -// PoolManager manager; -// address hookAddress; -// TestERC20 token0; -// TestERC20 token1; -// PoolId poolId; -// DiamondHookFutures hook = DiamondHookFutures( -// address( -// uint160( -// Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG -// | Hooks.AFTER_SWAP_FLAG -// ) -// ) -// ); -// PoolKey poolKey; - -// PoolSwapTest swapRouter; - -// uint24 constant PIPS = 1000000; -// int24 public tickSpacing = 10; -// uint24 public baseBeta = PIPS / 2; // % expressed as uint < 1e6 -// uint24 public decayRate = PIPS / 10; // % expressed as uint < 1e6 -// uint24 public vaultRedepositRate = PIPS / 10; // % expressed as uint < 1e6 -// // we also want to pass in a minimum constant amount (maybe even a % of total pool size, so the vault eventually empties) -// // if we only ever take 1% of the vault, the vault may never empty. -// uint24 public fee = 1000; // % expressed as uint < 1e6 - -// int24 public lowerTick; -// int24 public upperTick; - -// function setUp() public { -// token0 = new TestERC20(2 ** 128); -// token1 = new TestERC20(2 ** 128); - -// if (uint160(address(token0)) > uint160(address(token1))) { -// TestERC20 temp = token0; -// token0 = token1; -// token1 = temp; -// } - -// manager = new PoolManager(500000); - -// DiamondHookImpl impl = new DiamondHookImpl(manager, hook, tickSpacing, baseBeta, decayRate, vaultRedepositRate); -// (, bytes32[] memory writes) = vm.accesses(address(impl)); -// vm.etch(address(hook), address(impl).code); -// // for each storage key that was written during the hook implementation, copy the value over -// unchecked { -// for (uint256 i = 0; i < writes.length; i++) { -// bytes32 slot = writes[i]; -// vm.store(address(hook), slot, vm.load(address(impl), slot)); -// } -// } -// // Create the pool -// poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), fee, tickSpacing, hook); -// poolId = poolKey.toId(); -// uint256 price = 1; -// manager.initialize(poolKey, computeNewSQRTPrice(price)); -// swapRouter = new PoolSwapTest(manager); -// token0.approve(address(hook), type(uint256).max); -// token1.approve(address(hook), type(uint256).max); -// token0.approve(address(swapRouter), type(uint256).max); -// token1.approve(address(swapRouter), type(uint256).max); -// lowerTick = hook.lowerTick(); -// upperTick = hook.upperTick(); - -// assertEq(lowerTick, -887270); -// assertEq(upperTick, 887270); -// } - -// function testOpeningTotalSupplyZero() public { -// uint256 height = 1; -// vm.roll(height); - -// // do arb swap (price rises from initial) -// uint160 newSQRTPrice = computeNewSQRTPrice(4); - -// vm.expectRevert(abi.encodeWithSelector(DiamondHookFutures.TotalSupplyZero.selector)); - -// hook.openPool(newSQRTPrice); -// } - -// function testBasicArbSwap() public { -// // mint some liquidity -// hook.mint(10 ** 18, address(this)); -// uint256 height = 1; -// vm.roll(height); - -// // get starting values -// uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); -// uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); -// uint128 liquidityBefore = manager.getLiquidity(poolId); -// uint256 balance0ThisBefore = token0.balanceOf(address(this)); -// uint256 balance1ThisBefore = token1.balanceOf(address(this)); - -// // do arb swap (price rises from initial) -// uint160 newSQRTPrice = computeNewSQRTPrice(4); -// hook.openPool(newSQRTPrice); - -// // get ending values -// (uint160 newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); -// uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); -// uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); -// uint128 liquidityAfter = manager.getLiquidity(poolId); -// uint256 balance0ThisAfter = token0.balanceOf(address(this)); -// uint256 balance1ThisAfter = token1.balanceOf(address(this)); - -// // check expectations -// assertEq(newSQRTPriceCheck, newSQRTPrice); // pool price moved to where arb swap specified -// assertGt(balance1ManagerAfter, balance1ManagerBefore); // pool gained token0 -// assertGt(balance0ManagerBefore, balance0ManagerAfter); // pool lost token1 -// assertGt(balance1ThisBefore, balance1ThisAfter); // arber lost token0 -// assertGt(balance0ThisAfter, balance0ThisBefore); // arber gained token1 -// assertEq(balance0ThisAfter - balance0ThisBefore, balance0ManagerBefore - balance0ManagerAfter); // net is 0 -// assertEq(balance1ThisBefore - balance1ThisAfter, balance1ManagerAfter - balance1ManagerBefore); // net is 0 -// assertGt(liquidityBefore, liquidityAfter); // liquidity decreased - -// // reset starting values -// balance0ThisBefore = balance0ThisAfter; -// balance1ThisBefore = balance1ThisAfter; -// balance0ManagerBefore = balance0ManagerAfter; -// balance1ManagerBefore = balance1ManagerAfter; - -// // go to next block -// vm.roll(height + 1); - -// // do arb swap (price back down to initial) -// newSQRTPrice = computeNewSQRTPrice(1); -// hook.openPool(newSQRTPrice); - -// // get ending values -// balance0ThisAfter = token0.balanceOf(address(this)); -// balance1ThisAfter = token1.balanceOf(address(this)); -// balance0ManagerAfter = token0.balanceOf(address(manager)); -// balance1ManagerAfter = token1.balanceOf(address(manager)); -// (newSQRTPriceCheck,,,,,) = manager.getSlot0(poolId); - -// // check expectations -// assertEq(newSQRTPrice, newSQRTPriceCheck); -// assertGt(balance1ManagerBefore, balance1ManagerAfter); -// assertGt(balance0ManagerAfter, balance0ManagerBefore); -// assertGt(balance1ThisAfter, balance1ThisBefore); -// assertGt(balance0ThisBefore, balance0ThisAfter); -// assertEq(balance1ThisAfter - balance1ThisBefore, balance1ManagerBefore - balance1ManagerAfter); -// assertEq(balance0ThisBefore - balance0ThisAfter, balance0ManagerAfter - balance0ManagerBefore); - -// uint160 liquidityAfter2 = manager.getLiquidity(poolId); -// assertGt(liquidityAfter2, liquidityAfter); // liquidity actually increased (price moved back, can redeposit more vault) -// assertGt(liquidityBefore, liquidityAfter2); // but liquidity still less than originally -// } - -// function testManyWhipSaws() public { -// hook.mint(10 ** 18, address(this)); -// uint256 height = 1; -// uint256 price; -// uint160 newSQRTPrice; - -// uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); -// uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); -// (uint256 liquidity0Before, uint256 liquidity1Before) = getTokenReservesInPool(); - -// assertEq(balance0ManagerBefore - 1, liquidity0Before); -// assertEq(balance1ManagerBefore - 1, liquidity1Before); - -// for (uint256 i = 0; i < 5; i++) { -// vm.roll(height++); -// price = 4; -// newSQRTPrice = computeNewSQRTPrice(price); -// hook.openPool(newSQRTPrice); -// vm.roll(height++); -// price = 1; -// newSQRTPrice = computeNewSQRTPrice(price); -// hook.openPool(newSQRTPrice); -// } - -// uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); -// uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); -// (uint256 liquidity0After, uint256 liquidity1After) = getTokenReservesInPool(); - -// assertGt(liquidity0Before, liquidity0After); -// assertGt(liquidity1Before, liquidity1After); - -// uint256 undeposited0 = balance0ManagerAfter - liquidity0After; -// uint256 undeposited1 = balance1ManagerAfter - liquidity1After; -// uint256 dustThreshold = 100; - -// // should still have deposited almost ALL of of one or the other token (modulo some dust) -// assertGt(dustThreshold, undeposited0 > undeposited1 ? undeposited1 : undeposited0); -// } - -// function testWithdraw() public { -// hook.mint(10 ** 18, address(this)); -// uint256 totalSupply1 = hook.totalSupply(); -// assertEq(totalSupply1, 10 ** 18); - -// uint256 height = 1; -// uint256 price = 4; -// uint160 newSQRTPrice = computeNewSQRTPrice(price); -// hook.openPool(newSQRTPrice); - -// hook.burn(10 ** 16, address(this)); - -// uint256 totalSupply2 = hook.totalSupply(); -// assertEq(totalSupply2, totalSupply1 - 10 ** 16); - -// hook.burn(totalSupply2, address(this)); -// assertEq(hook.totalSupply(), 0); -// uint256 balance0Manager = token0.balanceOf(address(manager)); -// uint256 balance1Manager = token1.balanceOf(address(manager)); - -// // console.log(balance0Manager); -// // console.log(balance1Manager); - -// // this is kind of high IMO we are already somehow losing 3 wei in both tokens -// // seems like we may somehow losing track of 1 wei into the contract -// // not just for every openPool() but on other ops too? -// uint256 dustThreshold = 4; -// assertGt(dustThreshold, balance0Manager); -// assertGt(dustThreshold, balance1Manager); - -// hook.mint(10 ** 18, address(this)); - -// vm.roll(++height); -// price = 2; -// newSQRTPrice = computeNewSQRTPrice(price); -// hook.openPool(newSQRTPrice); - -// // test mint/burn invariant (you get back as much as you put in if nothing else changes (no swaps etc) -// uint256 balance0Before = token0.balanceOf(address(this)); -// uint256 balance1Before = token1.balanceOf(address(this)); - -// //console.log("midway balances:",token0.balanceOf(address(manager)),token1.balanceOf(address(manager))); -// hook.mint(10 ** 18, address(this)); -// hook.burn(10 ** 18, address(this)); -// hook.mint(10 ** 24, address(this)); -// hook.burn(10 ** 24, address(this)); - -// // NOTE this invariant is not working amounts are slightly off!!!! -// //assertEq(token0.balanceOf(address(this)), balance0Before); -// //assertEq(token1.balanceOf(address(this)), balance1Before); - -// vm.roll(++height); -// price = 4; -// newSQRTPrice = computeNewSQRTPrice(price); -// hook.openPool(newSQRTPrice); - -// hook.burn(10 ** 10, address(this)); -// vm.roll(++height); -// balance0Before = token0.balanceOf(address(this)); -// balance1Before = token1.balanceOf(address(this)); -// //console.log("Let's try a TOB mint"); -// // (uint160 poolPrice,,,,,)=manager.getSlot0(poolId); -// // (uint256 x, uint256 y)=getTokenReservesInPool(); -// // console.log("new block, pre-mint. reserves:",x,y,poolPrice); -// // console.log("new block, pre-mint. pool-price:",poolPrice); - -// hook.mint(10 ** 12, address(this)); -// // (poolPrice,,,,,)=manager.getSlot0(poolId); -// // (x, y)=getTokenReservesInPool(); -// // console.log("new block, post-mint. reserves:",x,y,poolPrice); -// // console.log("new block, post-mint. pool-price:",poolPrice); -// hook.openPool(newSQRTPrice); -// //console.log("before and after difference token 0:",token0.balanceOf(address(this))-balance0Before); -// //console.log("before and after difference token 1:",token1.balanceOf(address(this))-balance1Before); - -// vm.roll(++height); -// // (poolPrice,,,,,)=manager.getSlot0(poolId); -// // (x, y)=getTokenReservesInPool(); -// // console.log("new block, pre-burn. reserves:",x,y,poolPrice); -// // console.log("new block, pre-burn. pool-price:",poolPrice); -// hook.burn(10 ** 12, address(this)); -// // (poolPrice,,,,,)=manager.getSlot0(poolId); -// // (x, y)=getTokenReservesInPool(); -// // console.log("new block, post-burn. reserves:",x,y,poolPrice); -// // console.log("new block, post-burn. pool-price:",poolPrice); -// hook.openPool(newSQRTPrice); - -// hook.burn(hook.totalSupply(), address(this)); -// assertEq(hook.totalSupply(), 0); - -// balance0Manager = token0.balanceOf(address(manager)); -// balance1Manager = token1.balanceOf(address(manager)); -// dustThreshold = 18; -// assertGt(dustThreshold, balance0Manager); -// assertGt(dustThreshold, balance1Manager); -// } - -// function testSwaps() public { -// hook.mint(10 ** 20, address(this)); -// uint256 height = 1; -// uint256 price = 4; -// uint160 newSQRTPrice = computeNewSQRTPrice(price); - -// hook.openPool(newSQRTPrice); -// uint128 hedgeCommit0 = 10 ** 18; -// uint128 hedgeCommit1 = 10 ** 18; - -// // must deposit hedge tokens before swap can take place -// hook.depositHedgeCommitment(hedgeCommit0, hedgeCommit1); - -// // prepare swap token0 for token1 -// IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ -// zeroForOne: true, -// amountSpecified: 10 ** 15, -// sqrtPriceLimitX96: computeNewSQRTPrice(3) -// }); -// PoolSwapTest.TestSettings memory settings = -// PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - -// uint256 balance0Before = token0.balanceOf(address(this)); -// uint256 balance1Before = token1.balanceOf(address(this)); - -// swapRouter.swap(poolKey, params, settings); - -// uint256 balance0After = token0.balanceOf(address(this)); -// uint256 balance1After = token1.balanceOf(address(this)); - -// uint256 hedgeRequired0 = hook.hedgeRequired0(); -// uint256 hedgeRequired1 = hook.hedgeRequired1(); - -// assertGt(balance0Before, balance0After); -// assertGt(balance1After, balance1Before); -// assertGt(hedgeRequired1, 0); -// assertEq(hedgeRequired0, 0); -// assertEq(balance1After - balance1Before, hedgeRequired1); - -// // now the swapper is going to sell token 1s for 0s back to the pool -// // in approx the same size as before -// // to move the pool price back to original price -// params = IPoolManager.SwapParams({ -// zeroForOne: false, -// amountSpecified: -int256(hedgeRequired1), -// sqrtPriceLimitX96: computeNewSQRTPrice(5) -// }); - -// balance0Before = token0.balanceOf(address(this)); -// balance1Before = token1.balanceOf(address(this)); - -// swapRouter.swap(poolKey, params, settings); - -// balance0After = token0.balanceOf(address(this)); -// balance1After = token1.balanceOf(address(this)); - -// hedgeRequired0 = hook.hedgeRequired0(); -// hedgeRequired1 = hook.hedgeRequired1(); - -// assertGt(balance1Before, balance1After); -// assertGt(balance0After, balance0Before); -// assertGt(hedgeRequired0, 0); -// assertEq(hedgeRequired1, 0); - -// hook.withdrawHedgeCommitment(hedgeCommit0 - hedgeRequired0, hedgeCommit1); - -// params = IPoolManager.SwapParams({ -// zeroForOne: true, -// amountSpecified: 2 ** 80, -// sqrtPriceLimitX96: computeNewSQRTPrice(4) -// }); - -// balance0Before = token0.balanceOf(address(this)); -// balance1Before = token1.balanceOf(address(this)); - -// swapRouter.swap(poolKey, params, settings); - -// balance0After = token0.balanceOf(address(this)); -// balance1After = token1.balanceOf(address(this)); - -// hedgeRequired0 = hook.hedgeRequired0(); -// hedgeRequired1 = hook.hedgeRequired1(); - -// assertGt(balance0Before, balance0After); -// assertGt(balance1After, balance1Before); -// assertEq(hedgeRequired0, 0); -// assertEq(hedgeRequired1, 0); - -// hook.withdrawHedgeCommitment(hook.hedgeCommitted0(), 0); - -// assertEq(hook.hedgeCommitted0(), 0); -// assertEq(hook.hedgeCommitted1(), 0); - -// vm.roll(++height); -// price = 2; -// newSQRTPrice = computeNewSQRTPrice(price); -// hook.openPool(newSQRTPrice); -// } - -// function _sqrt(uint256 x) internal pure returns (uint256 y) { -// uint256 z = (x + 1) / 2; -// y = x; -// while (z < y) { -// y = z; -// z = (x / z + z) / 2; -// } -// } - -// function computeNewSQRTPrice(uint256 price) internal pure returns (uint160 y) { -// y = uint160(_sqrt(price * 2 ** 96) * 2 ** 48); -// } - -// function getTokenReservesInPool() public view returns (uint256 x, uint256 y) { -// Position.Info memory info = manager.getPosition(poolId, address(hook), lowerTick, upperTick); -// (uint160 poolPrice,,,,,) = manager.getSlot0(poolId); -// (x, y) = LiquidityAmounts.getAmountsForLiquidity( -// poolPrice, TickMath.getSqrtRatioAtTick(lowerTick), TickMath.getSqrtRatioAtTick(upperTick), info.liquidity -// ); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey, PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {LiquidityAmounts} from "../src/libraries/LiquidityAmounts.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {console} from "forge-std/console.sol"; + +import {DiamondHookFutures} from "../src/DiamondHookFutures.sol"; +import {DiamondHookImpl} from "./utils/DiamondHookImpl.sol"; + +contract TestDiamondHook is Test, Deployers, GasSnapshot { + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + + PoolManager manager; + address hookAddress; + TestERC20 token0; + TestERC20 token1; + PoolId poolId; + DiamondHookFutures hook = DiamondHookFutures( + address( + uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG + | Hooks.AFTER_SWAP_FLAG + ) + ) + ); + PoolKey poolKey; + + PoolSwapTest swapRouter; + + uint24 constant PIPS = 1000000; + int24 public tickSpacing = 10; + uint24 public baseBeta = PIPS / 2; // % expressed as uint < 1e6 + uint24 public decayRate = PIPS / 10; // % expressed as uint < 1e6 + uint24 public vaultRedepositRate = PIPS / 10; // % expressed as uint < 1e6 + // we also want to pass in a minimum constant amount (maybe even a % of total pool size, so the vault eventually empties) + // if we only ever take 1% of the vault, the vault may never empty. + uint24 public fee = 1000; // % expressed as uint < 1e6 + + int24 public lowerTick; + int24 public upperTick; + + function setUp() public { + token0 = new TestERC20(2 ** 128); + token1 = new TestERC20(2 ** 128); + + if (uint160(address(token0)) > uint160(address(token1))) { + TestERC20 temp = token0; + token0 = token1; + token1 = temp; + } + + manager = new PoolManager(500000); + + DiamondHookImpl impl = new DiamondHookImpl(manager, hook, tickSpacing, baseBeta, decayRate, vaultRedepositRate); + (, bytes32[] memory writes) = vm.accesses(address(impl)); + vm.etch(address(hook), address(impl).code); + // for each storage key that was written during the hook implementation, copy the value over + unchecked { + for (uint256 i = 0; i < writes.length; i++) { + bytes32 slot = writes[i]; + vm.store(address(hook), slot, vm.load(address(impl), slot)); + } + } + // Create the pool + poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), fee, tickSpacing, hook); + poolId = poolKey.toId(); + uint256 price = 1; + manager.initialize(poolKey, computeNewSQRTPrice(price), new bytes(0)); + swapRouter = new PoolSwapTest(manager); + token0.approve(address(hook), type(uint256).max); + token1.approve(address(hook), type(uint256).max); + token0.approve(address(swapRouter), type(uint256).max); + token1.approve(address(swapRouter), type(uint256).max); + lowerTick = hook.lowerTick(); + upperTick = hook.upperTick(); + + assertEq(lowerTick, -887270); + assertEq(upperTick, 887270); + } + + function testOpeningTotalSupplyZero() public { + uint256 height = 1; + vm.roll(height); + + // do arb swap (price rises from initial) + uint160 newSQRTPrice = computeNewSQRTPrice(4); + + vm.expectRevert(abi.encodeWithSelector(DiamondHookFutures.TotalSupplyZero.selector)); + + hook.openPool(newSQRTPrice); + } + + function testBasicArbSwap() public { + // mint some liquidity + hook.mint(10 ** 18, address(this)); + uint256 height = 1; + vm.roll(height); + + // get starting values + uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); + uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); + uint128 liquidityBefore = manager.getLiquidity(poolId); + uint256 balance0ThisBefore = token0.balanceOf(address(this)); + uint256 balance1ThisBefore = token1.balanceOf(address(this)); + + // do arb swap (price rises from initial) + uint160 newSQRTPrice = computeNewSQRTPrice(4); + hook.openPool(newSQRTPrice); + + // get ending values + (uint160 newSQRTPriceCheck,,,) = manager.getSlot0(poolId); + uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); + uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); + uint128 liquidityAfter = manager.getLiquidity(poolId); + uint256 balance0ThisAfter = token0.balanceOf(address(this)); + uint256 balance1ThisAfter = token1.balanceOf(address(this)); + + // check expectations + assertEq(newSQRTPriceCheck, newSQRTPrice); // pool price moved to where arb swap specified + assertGt(balance1ManagerAfter, balance1ManagerBefore); // pool gained token0 + assertGt(balance0ManagerBefore, balance0ManagerAfter); // pool lost token1 + assertGt(balance1ThisBefore, balance1ThisAfter); // arber lost token0 + assertGt(balance0ThisAfter, balance0ThisBefore); // arber gained token1 + assertEq(balance0ThisAfter - balance0ThisBefore, balance0ManagerBefore - balance0ManagerAfter); // net is 0 + assertEq(balance1ThisBefore - balance1ThisAfter, balance1ManagerAfter - balance1ManagerBefore); // net is 0 + assertGt(liquidityBefore, liquidityAfter); // liquidity decreased + + // reset starting values + balance0ThisBefore = balance0ThisAfter; + balance1ThisBefore = balance1ThisAfter; + balance0ManagerBefore = balance0ManagerAfter; + balance1ManagerBefore = balance1ManagerAfter; + + // go to next block + vm.roll(height + 1); + + // do arb swap (price back down to initial) + newSQRTPrice = computeNewSQRTPrice(1); + hook.openPool(newSQRTPrice); + + // get ending values + balance0ThisAfter = token0.balanceOf(address(this)); + balance1ThisAfter = token1.balanceOf(address(this)); + balance0ManagerAfter = token0.balanceOf(address(manager)); + balance1ManagerAfter = token1.balanceOf(address(manager)); + (newSQRTPriceCheck,,,) = manager.getSlot0(poolId); + + // check expectations + assertEq(newSQRTPrice, newSQRTPriceCheck); + assertGt(balance1ManagerBefore, balance1ManagerAfter); + assertGt(balance0ManagerAfter, balance0ManagerBefore); + assertGt(balance1ThisAfter, balance1ThisBefore); + assertGt(balance0ThisBefore, balance0ThisAfter); + assertEq(balance1ThisAfter - balance1ThisBefore, balance1ManagerBefore - balance1ManagerAfter); + assertEq(balance0ThisBefore - balance0ThisAfter, balance0ManagerAfter - balance0ManagerBefore); + + uint160 liquidityAfter2 = manager.getLiquidity(poolId); + assertGt(liquidityAfter2, liquidityAfter); // liquidity actually increased (price moved back, can redeposit more vault) + assertGt(liquidityBefore, liquidityAfter2); // but liquidity still less than originally + } + + function testManyWhipSaws() public { + hook.mint(10 ** 18, address(this)); + uint256 height = 1; + uint256 price; + uint160 newSQRTPrice; + + uint256 balance0ManagerBefore = token0.balanceOf(address(manager)); + uint256 balance1ManagerBefore = token1.balanceOf(address(manager)); + (uint256 liquidity0Before, uint256 liquidity1Before) = getTokenReservesInPool(); + + assertEq(balance0ManagerBefore - 1, liquidity0Before); + assertEq(balance1ManagerBefore - 1, liquidity1Before); + + for (uint256 i = 0; i < 5; i++) { + vm.roll(height++); + price = 4; + newSQRTPrice = computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + vm.roll(height++); + price = 1; + newSQRTPrice = computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + } + + uint256 balance0ManagerAfter = token0.balanceOf(address(manager)); + uint256 balance1ManagerAfter = token1.balanceOf(address(manager)); + (uint256 liquidity0After, uint256 liquidity1After) = getTokenReservesInPool(); + + assertGt(liquidity0Before, liquidity0After); + assertGt(liquidity1Before, liquidity1After); + + uint256 undeposited0 = balance0ManagerAfter - liquidity0After; + uint256 undeposited1 = balance1ManagerAfter - liquidity1After; + uint256 dustThreshold = 100; + + // should still have deposited almost ALL of of one or the other token (modulo some dust) + assertGt(dustThreshold, undeposited0 > undeposited1 ? undeposited1 : undeposited0); + } + + function testWithdraw() public { + hook.mint(10 ** 18, address(this)); + uint256 totalSupply1 = hook.totalSupply(); + assertEq(totalSupply1, 10 ** 18); + + uint256 height = 1; + uint256 price = 4; + uint160 newSQRTPrice = computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + + hook.burn(10 ** 16, address(this)); + + uint256 totalSupply2 = hook.totalSupply(); + assertEq(totalSupply2, totalSupply1 - 10 ** 16); + + hook.burn(totalSupply2, address(this)); + assertEq(hook.totalSupply(), 0); + uint256 balance0Manager = token0.balanceOf(address(manager)); + uint256 balance1Manager = token1.balanceOf(address(manager)); + + // console.log(balance0Manager); + // console.log(balance1Manager); + + // this is kind of high IMO we are already somehow losing 3 wei in both tokens + // seems like we may somehow losing track of 1 wei into the contract + // not just for every openPool() but on other ops too? + uint256 dustThreshold = 4; + assertGt(dustThreshold, balance0Manager); + assertGt(dustThreshold, balance1Manager); + + hook.mint(10 ** 18, address(this)); + + vm.roll(++height); + price = 2; + newSQRTPrice = computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + + // test mint/burn invariant (you get back as much as you put in if nothing else changes (no swaps etc) + uint256 balance0Before = token0.balanceOf(address(this)); + uint256 balance1Before = token1.balanceOf(address(this)); + + //console.log("midway balances:",token0.balanceOf(address(manager)),token1.balanceOf(address(manager))); + hook.mint(10 ** 18, address(this)); + hook.burn(10 ** 18, address(this)); + hook.mint(10 ** 24, address(this)); + hook.burn(10 ** 24, address(this)); + + // NOTE this invariant is not working amounts are slightly off!!!! + //assertEq(token0.balanceOf(address(this)), balance0Before); + //assertEq(token1.balanceOf(address(this)), balance1Before); + + vm.roll(++height); + price = 4; + newSQRTPrice = computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + + hook.burn(10 ** 10, address(this)); + vm.roll(++height); + balance0Before = token0.balanceOf(address(this)); + balance1Before = token1.balanceOf(address(this)); + //console.log("Let's try a TOB mint"); + // (uint160 poolPrice,,,,,)=manager.getSlot0(poolId); + // (uint256 x, uint256 y)=getTokenReservesInPool(); + // console.log("new block, pre-mint. reserves:",x,y,poolPrice); + // console.log("new block, pre-mint. pool-price:",poolPrice); + + hook.mint(10 ** 12, address(this)); + // (poolPrice,,,,,)=manager.getSlot0(poolId); + // (x, y)=getTokenReservesInPool(); + // console.log("new block, post-mint. reserves:",x,y,poolPrice); + // console.log("new block, post-mint. pool-price:",poolPrice); + hook.openPool(newSQRTPrice); + //console.log("before and after difference token 0:",token0.balanceOf(address(this))-balance0Before); + //console.log("before and after difference token 1:",token1.balanceOf(address(this))-balance1Before); + + vm.roll(++height); + // (poolPrice,,,,,)=manager.getSlot0(poolId); + // (x, y)=getTokenReservesInPool(); + // console.log("new block, pre-burn. reserves:",x,y,poolPrice); + // console.log("new block, pre-burn. pool-price:",poolPrice); + hook.burn(10 ** 12, address(this)); + // (poolPrice,,,,,)=manager.getSlot0(poolId); + // (x, y)=getTokenReservesInPool(); + // console.log("new block, post-burn. reserves:",x,y,poolPrice); + // console.log("new block, post-burn. pool-price:",poolPrice); + hook.openPool(newSQRTPrice); + + hook.burn(hook.totalSupply(), address(this)); + assertEq(hook.totalSupply(), 0); + + balance0Manager = token0.balanceOf(address(manager)); + balance1Manager = token1.balanceOf(address(manager)); + dustThreshold = 18; + assertGt(dustThreshold, balance0Manager); + assertGt(dustThreshold, balance1Manager); + } + + function testSwaps() public { + hook.mint(10 ** 20, address(this)); + uint256 height = 1; + uint256 price = 4; + uint160 newSQRTPrice = computeNewSQRTPrice(price); + + hook.openPool(newSQRTPrice); + uint128 hedgeCommit0 = 10 ** 18; + uint128 hedgeCommit1 = 10 ** 18; + + // must deposit hedge tokens before swap can take place + hook.depositHedgeCommitment(hedgeCommit0, hedgeCommit1); + + // prepare swap token0 for token1 + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: 10 ** 15, + sqrtPriceLimitX96: computeNewSQRTPrice(3) + }); + PoolSwapTest.TestSettings memory settings = + PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + + uint256 balance0Before = token0.balanceOf(address(this)); + uint256 balance1Before = token1.balanceOf(address(this)); + + swapRouter.swap(poolKey, params, settings, new bytes(0)); + + uint256 balance0After = token0.balanceOf(address(this)); + uint256 balance1After = token1.balanceOf(address(this)); + + uint256 hedgeRequired0 = hook.hedgeRequired0(); + uint256 hedgeRequired1 = hook.hedgeRequired1(); + + assertGt(balance0Before, balance0After); + assertGt(balance1After, balance1Before); + assertGt(hedgeRequired1, 0); + assertEq(hedgeRequired0, 0); + assertEq(balance1After - balance1Before, hedgeRequired1); + + // now the swapper is going to sell token 1s for 0s back to the pool + // in approx the same size as before + // to move the pool price back to original price + params = IPoolManager.SwapParams({ + zeroForOne: false, + amountSpecified: -int256(hedgeRequired1), + sqrtPriceLimitX96: computeNewSQRTPrice(5) + }); + + balance0Before = token0.balanceOf(address(this)); + balance1Before = token1.balanceOf(address(this)); + + swapRouter.swap(poolKey, params, settings, new bytes(0)); + + balance0After = token0.balanceOf(address(this)); + balance1After = token1.balanceOf(address(this)); + + hedgeRequired0 = hook.hedgeRequired0(); + hedgeRequired1 = hook.hedgeRequired1(); + + assertGt(balance1Before, balance1After); + assertGt(balance0After, balance0Before); + assertGt(hedgeRequired0, 0); + assertEq(hedgeRequired1, 0); + + hook.withdrawHedgeCommitment(hedgeCommit0 - hedgeRequired0, hedgeCommit1); + + params = IPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: 2 ** 80, + sqrtPriceLimitX96: computeNewSQRTPrice(4) + }); + + balance0Before = token0.balanceOf(address(this)); + balance1Before = token1.balanceOf(address(this)); + + swapRouter.swap(poolKey, params, settings, new bytes(0)); + + balance0After = token0.balanceOf(address(this)); + balance1After = token1.balanceOf(address(this)); + + hedgeRequired0 = hook.hedgeRequired0(); + hedgeRequired1 = hook.hedgeRequired1(); + + assertGt(balance0Before, balance0After); + assertGt(balance1After, balance1Before); + assertEq(hedgeRequired0, 0); + assertEq(hedgeRequired1, 0); + + hook.withdrawHedgeCommitment(hook.hedgeCommitted0(), 0); + + assertEq(hook.hedgeCommitted0(), 0); + assertEq(hook.hedgeCommitted1(), 0); + + vm.roll(++height); + price = 2; + newSQRTPrice = computeNewSQRTPrice(price); + hook.openPool(newSQRTPrice); + } + + function _sqrt(uint256 x) internal pure returns (uint256 y) { + uint256 z = (x + 1) / 2; + y = x; + while (z < y) { + y = z; + z = (x / z + z) / 2; + } + } + + function computeNewSQRTPrice(uint256 price) internal pure returns (uint160 y) { + y = uint160(_sqrt(price * 2 ** 96) * 2 ** 48); + } + + function getTokenReservesInPool() public view returns (uint256 x, uint256 y) { + Position.Info memory info = manager.getPosition(poolId, address(hook), lowerTick, upperTick); + (uint160 poolPrice,,,) = manager.getSlot0(poolId); + (x, y) = LiquidityAmounts.getAmountsForLiquidity( + poolPrice, TickMath.getSqrtRatioAtTick(lowerTick), TickMath.getSqrtRatioAtTick(upperTick), info.liquidity + ); + } +}