2024-03-18 12:42:30 +01:00
// SPDX-License-Identifier: GPL-3.0-or-later
2024-03-28 19:55:01 +01:00
pragma solidity ^ 0 . 8 . 19 ;
2024-03-18 12:42:30 +01:00
import " @uniswap-v3-periphery/libraries/PositionKey.sol " ;
import " @uniswap-v3-core/libraries/FixedPoint128.sol " ;
import " @uniswap-v3-core/interfaces/IUniswapV3Pool.sol " ;
import " @aperture/uni-v3-lib/TickMath.sol " ;
2024-07-04 10:24:06 +02:00
import { LiquidityAmounts } from " @aperture/uni-v3-lib/LiquidityAmounts.sol " ;
2024-03-18 12:42:30 +01:00
import " @aperture/uni-v3-lib/PoolAddress.sol " ;
import " @aperture/uni-v3-lib/CallbackValidation.sol " ;
import " @openzeppelin/token/ERC20/IERC20.sol " ;
2024-04-28 07:00:53 +02:00
import " @openzeppelin/utils/math/SignedMath.sol " ;
2024-08-12 19:07:07 +02:00
import { Math } from " @openzeppelin/utils/math/Math.sol " ;
2024-03-28 21:50:22 +01:00
import { ABDKMath64x64 } from " @abdk/ABDKMath64x64.sol " ;
2024-03-18 12:42:30 +01:00
import " ./interfaces/IWETH9.sol " ;
2024-07-18 07:35:39 +02:00
import { Harberg } from " ./Harberg.sol " ;
2025-02-01 21:49:15 +01:00
import { Optimizer } from " ./Optimizer.sol " ;
2025-07-08 10:31:41 +02:00
import { VWAPTracker } from " ./VWAPTracker.sol " ;
2024-03-28 21:50:22 +01:00
2024-03-18 12:42:30 +01:00
/**
2024-07-18 07:35:39 +02:00
* @ title LiquidityManager for Harberg Token on Uniswap V3
* @ notice Manages liquidity provisioning on Uniswap V3 for the Harberg token by maintaining three distinct positions :
* - Floor Position : Ensures a minimum price support by having enough reserve assets to potentially buy back the circulating supply of Harberg .
2024-07-13 18:33:47 +02:00
* - Anchor Position : Provides liquidity around the current market price to facilitate trading and maintain market stability .
2024-07-18 07:35:39 +02:00
* - Discovery Position : Expands liquidity by minting new Harberg tokens as the price rises , capturing potential growth in the ecosystem .
* The contract dynamically adjusts these positions in response to market movements to maintain strategic liquidity levels and support the Harberg token ' s price.
2024-07-13 18:33:47 +02:00
* It also collects and transfers fees generated from trading activities to a designated fee destination .
2024-08-13 17:06:12 +03:00
* @ dev Utilizes Uniswap V3 ' s concentrated liquidity feature, enabling highly efficient use of capital.
2024-07-13 18:33:47 +02:00
* /
2025-07-08 10:31:41 +02:00
contract LiquidityManager is VWAPTracker {
2024-08-12 19:07:07 +02:00
using Math for uint256 ;
2024-07-16 19:47:39 +02:00
// the minimum granularity of liquidity positions in the Uniswap V3 pool. this is a 1% pool.
2025-07-08 10:31:41 +02:00
2024-07-09 18:00:39 +02:00
int24 internal constant TICK_SPACING = 200 ;
2024-07-16 19:47:39 +02:00
// DISCOVERY_SPACING determines the range above the current price where new tokens are minted and sold.
2024-08-15 15:17:44 +02:00
// 11000 ticks represent 3x the current price
2024-07-09 18:00:39 +02:00
int24 internal constant DISCOVERY_SPACING = 11000 ;
2024-07-16 19:47:39 +02:00
// how much more liquidity per tick discovery is holding over anchor
2025-02-01 21:49:15 +01:00
uint128 internal constant MIN_DISCOVERY_DEPTH = 200 ; // 500 // 500%
2024-07-16 19:47:39 +02:00
// only working with UNI V3 1% fee tier pools
2024-07-09 18:00:39 +02:00
uint24 internal constant FEE = uint24 ( 10 _000 ) ;
2024-07-18 16:50:23 +02:00
// used to double-check price with uni oracle
uint32 internal constant PRICE_STABILITY_INTERVAL = 300 ; // 5 minutes in seconds
2024-08-15 15:17:44 +02:00
int24 internal constant MAX_TICK_DEVIATION = 50 ; // how much is that?
2024-07-16 19:47:39 +02:00
2024-03-18 12:42:30 +01:00
// the address of the Uniswap V3 factory
2024-07-13 14:56:13 +02:00
address private immutable factory ;
IWETH9 private immutable weth ;
2024-07-18 07:35:39 +02:00
Harberg private immutable harb ;
2025-02-01 21:49:15 +01:00
Optimizer private immutable optimizer ;
2024-07-13 14:56:13 +02:00
IUniswapV3Pool private immutable pool ;
bool private immutable token0isWeth ;
2024-07-09 18:00:39 +02:00
PoolKey private poolKey ;
2024-09-12 17:00:24 +02:00
address private recenterAccess ;
2024-03-18 12:42:30 +01:00
2024-07-16 20:47:06 +02:00
// the 3 positions this contract is managing
2025-07-08 10:31:41 +02:00
enum Stage {
FLOOR ,
ANCHOR ,
DISCOVERY
}
2024-07-16 20:47:06 +02:00
2024-03-18 12:42:30 +01:00
struct TokenPosition {
// the liquidity of the position
uint128 liquidity ;
int24 tickLower ;
int24 tickUpper ;
}
2024-07-16 19:47:39 +02:00
mapping ( Stage => TokenPosition ) public positions ;
// the address where liquidity fees will be sent
2024-07-09 18:00:39 +02:00
address public feeDestination ;
2024-03-18 12:42:30 +01:00
2024-07-13 14:56:13 +02:00
error ZeroAddressInSetter ( ) ;
error AddressAlreadySet ( ) ;
2025-02-01 21:49:15 +01:00
event EthScarcity ( int24 currentTick , uint256 ethBalance , uint256 outstandingSupply , uint256 vwap , int24 vwapTick ) ;
event EthAbundance ( int24 currentTick , uint256 ethBalance , uint256 outstandingSupply , uint256 vwap , int24 vwapTick ) ;
2024-07-16 19:47:39 +02:00
/// @dev Function modifier to ensure that the caller is the feeDestination
modifier onlyFeeDestination ( ) {
require ( msg . sender == address ( feeDestination ) , " only callable by feeDestination " ) ;
_ ;
}
2024-03-18 12:42:30 +01:00
2024-07-18 07:35:39 +02:00
/// @notice Creates a liquidity manager for managing Harberg token liquidity on Uniswap V3.
2024-07-13 18:33:47 +02:00
/// @param _factory The address of the Uniswap V3 factory.
/// @param _WETH9 The address of the WETH contract for handling ETH in trades.
2024-07-18 07:35:39 +02:00
/// @param _harb The address of the Harberg token contract.
/// @dev Computes the Uniswap pool address for the Harberg-WETH pair and sets up the initial configuration for the liquidity manager.
2025-02-01 21:49:15 +01:00
constructor ( address _factory , address _WETH9 , address _harb , address _optimizer ) {
2024-03-18 12:42:30 +01:00
factory = _factory ;
weth = IWETH9 ( _WETH9 ) ;
poolKey = PoolAddress . getPoolKey ( _WETH9 , _harb , FEE ) ;
pool = IUniswapV3Pool ( PoolAddress . computeAddress ( factory , poolKey ) ) ;
2024-07-18 07:35:39 +02:00
harb = Harberg ( _harb ) ;
2024-03-18 12:42:30 +01:00
token0isWeth = _WETH9 < _harb ;
2025-07-08 10:31:41 +02:00
optimizer = Optimizer ( _optimizer ) ;
2024-03-18 12:42:30 +01:00
}
2024-07-13 18:33:47 +02:00
/// @notice Callback function that Uniswap V3 calls for liquidity actions requiring minting or burning of tokens.
/// @param amount0Owed The amount of token0 owed for the liquidity provision.
/// @param amount1Owed The amount of token1 owed for the liquidity provision.
2024-07-18 07:35:39 +02:00
/// @dev This function mints Harberg tokens as needed and handles WETH deposits for ETH conversions during liquidity interactions.
2024-03-18 12:42:30 +01:00
function uniswapV3MintCallback ( uint256 amount0Owed , uint256 amount1Owed , bytes calldata ) external {
CallbackValidation . verifyCallback ( factory , poolKey ) ;
2024-04-11 07:28:54 +02:00
// take care of harb
2024-08-16 12:00:13 +02:00
uint256 harbPulled = token0isWeth ? amount1Owed : amount0Owed ;
2024-08-15 15:17:44 +02:00
harb . mint ( harbPulled ) ;
2024-04-11 07:28:54 +02:00
// pack ETH
2024-04-28 07:00:53 +02:00
uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed ;
if ( weth . balanceOf ( address ( this ) ) < ethOwed ) {
weth . deposit { value : address ( this ) . balance } ( ) ;
}
2024-04-23 06:58:34 +02:00
// do transfers
2024-03-18 12:42:30 +01:00
if ( amount0Owed > 0 ) IERC20 ( poolKey . token0 ) . transfer ( msg . sender , amount0Owed ) ;
if ( amount1Owed > 0 ) IERC20 ( poolKey . token1 ) . transfer ( msg . sender , amount1Owed ) ;
}
2024-07-13 18:33:47 +02:00
/// @notice Sets the address to which trading fees are transferred.
/// @param feeDestination_ The address that will receive the collected trading fees.
/// @dev Can only be called once to set the fee destination, further attempts will revert.
2024-06-09 16:06:41 +02:00
function setFeeDestination ( address feeDestination_ ) external {
2024-07-13 14:56:13 +02:00
if ( address ( 0 ) == feeDestination_ ) revert ZeroAddressInSetter ( ) ;
if ( feeDestination != address ( 0 ) ) revert AddressAlreadySet ( ) ;
2024-06-09 16:06:41 +02:00
feeDestination = feeDestination_ ;
}
2024-09-12 17:00:24 +02:00
function setRecenterAccess ( address addr ) external onlyFeeDestination {
recenterAccess = addr ;
}
function revokeRecenterAccess ( ) external onlyFeeDestination {
recenterAccess = address ( 0 ) ;
}
2025-07-08 10:31:41 +02:00
receive ( ) external payable { }
2025-07-06 10:08:59 +02:00
2024-07-18 07:35:39 +02:00
/// @notice Calculates the Uniswap V3 tick corresponding to a given price ratio between Harberg and ETH.
2024-07-13 18:33:47 +02:00
/// @param t0isWeth Boolean flag indicating if token0 is WETH.
2024-07-18 07:35:39 +02:00
/// @param tokenAmount Amount of the Harberg token.
2024-07-13 18:33:47 +02:00
/// @param ethAmount Amount of Ethereum.
/// @return tick_ The calculated tick for the given price ratio.
2024-07-09 18:00:39 +02:00
function tickAtPrice ( bool t0isWeth , uint256 tokenAmount , uint256 ethAmount ) internal pure returns ( int24 tick_ ) {
2024-03-28 21:50:22 +01:00
require ( ethAmount > 0 , " ETH amount cannot be zero " ) ;
2024-04-11 07:28:54 +02:00
if ( tokenAmount == 0 ) {
2024-08-13 17:06:12 +03:00
// HARB/ETH
tick_ = TickMath . MAX_TICK ;
2024-04-11 07:28:54 +02:00
} else {
// Use a fixed-point library or more precise arithmetic for the division here.
// For example, using ABDKMath64x64 for a more precise division and square root calculation.
2025-07-08 10:31:41 +02:00
int128 priceRatioX64 = ABDKMath64x64 . div ( int128 ( int256 ( tokenAmount ) ) , int128 ( int256 ( ethAmount ) ) ) ;
2024-08-13 17:06:12 +03:00
// HARB/ETH
tick_ = tickAtPriceRatio ( priceRatioX64 ) ;
2024-04-11 07:28:54 +02:00
}
2024-08-13 17:06:12 +03:00
// convert to tick in a pool
tick_ = t0isWeth ? tick_ : - tick_ ;
2024-08-12 19:07:07 +02:00
}
2024-08-13 17:06:12 +03:00
function tickAtPriceRatio ( int128 priceRatioX64 ) internal pure returns ( int24 tick_ ) {
2024-08-12 19:07:07 +02:00
// Convert the price ratio into a sqrt price in the format expected by Uniswap's TickMath.
2025-07-08 10:31:41 +02:00
uint160 sqrtPriceX96 = uint160 ( int160 ( ABDKMath64x64 . sqrt ( priceRatioX64 ) << 32 ) ) ;
2024-03-28 21:50:22 +01:00
tick_ = TickMath . getTickAtSqrtRatio ( sqrtPriceX96 ) ;
2024-07-09 18:00:39 +02:00
}
2024-08-12 19:07:07 +02:00
/// @notice Calculates the price ratio from a given Uniswap V3 tick as HARB/ETH.
2024-07-13 18:33:47 +02:00
/// @param tick The tick for which to calculate the price ratio.
2024-08-12 19:07:07 +02:00
/// @return priceRatioX96 The price ratio corresponding to the given tick.
function priceAtTick ( int24 tick ) private pure returns ( uint256 priceRatioX96 ) {
//tick = (tick < 0) ? -tick : tick;
uint256 sqrtRatioX96 = TickMath . getSqrtRatioAtTick ( tick ) ;
priceRatioX96 = sqrtRatioX96 . mulDiv ( sqrtRatioX96 , ( 1 << 96 ) ) ;
2024-03-28 19:55:01 +01:00
}
2024-07-13 18:33:47 +02:00
/// @notice Internal function to mint liquidity positions in the Uniswap V3 pool.
/// @param stage The liquidity stage (floor, anchor, discovery) being adjusted.
/// @param tickLower The lower bound of the tick range for the position.
/// @param tickUpper The upper bound of the tick range for the position.
/// @param liquidity The amount of liquidity to mint at the specified range.
2024-04-27 07:04:33 +02:00
function _mint ( Stage stage , int24 tickLower , int24 tickUpper , uint128 liquidity ) internal {
2024-03-28 21:50:22 +01:00
// create position
2025-07-08 10:31:41 +02:00
pool . mint ( address ( this ) , tickLower , tickUpper , liquidity , abi . encode ( poolKey ) ) ;
2024-03-28 21:50:22 +01:00
// put into storage
2025-07-08 10:31:41 +02:00
positions [ stage ] = TokenPosition ( { liquidity : liquidity , tickLower : tickLower , tickUpper : tickUpper } ) ;
2024-03-28 21:50:22 +01:00
}
2025-07-06 10:08:59 +02:00
/// @notice Clamps tick to valid range and aligns to tick spacing
/// @param tick The tick to clamp
/// @return clampedTick The clamped and aligned tick
function _clampToTickSpacing ( int24 tick ) internal pure returns ( int24 clampedTick ) {
// Align to tick spacing first
clampedTick = tick / TICK_SPACING * TICK_SPACING ;
2025-07-08 10:31:41 +02:00
2025-07-06 10:08:59 +02:00
// Ensure tick is within valid bounds (this should rarely be needed due to extreme price checks)
if ( clampedTick < TickMath . MIN_TICK ) clampedTick = TickMath . MIN_TICK ;
if ( clampedTick > TickMath . MAX_TICK ) clampedTick = TickMath . MAX_TICK ;
}
2024-07-13 18:33:47 +02:00
/// @notice Internal function to set or adjust the floor, anchor, and discovery positions based on current market conditions and the manager's strategy.
/// @param currentTick The current market tick.
/// @dev Recalculates and realigns all liquidity positions according to the latest market data and strategic requirements.
2025-07-08 10:31:41 +02:00
function _set (
int24 currentTick ,
uint256 capitalInefficiency ,
uint256 anchorShare ,
uint24 anchorWidth ,
uint256 discoveryDepth
) internal {
2024-08-15 15:17:44 +02:00
uint256 ethBalance = ( address ( this ) . balance + weth . balanceOf ( address ( this ) ) ) ;
2024-11-07 15:33:40 +00:00
// this enforces an floor liquidity share of 75% to 95 %;
2025-07-08 10:31:41 +02:00
uint256 floorEthBalance = ( 19 * ethBalance / 20 ) - ( 2 * anchorShare * ethBalance / 10 ** 19 ) ;
2024-08-15 15:17:44 +02:00
// set Anchor position
2024-08-16 12:00:13 +02:00
uint256 pulledHarb ;
2025-02-01 21:49:15 +01:00
// this enforces a anchor range of 1% to 100% of the price
int24 anchorSpacing = TICK_SPACING + ( 34 * int24 ( anchorWidth ) * TICK_SPACING / 100 ) ;
2024-08-15 15:17:44 +02:00
{
2025-07-06 10:08:59 +02:00
int24 tickLower = _clampToTickSpacing ( currentTick - anchorSpacing ) ;
int24 tickUpper = _clampToTickSpacing ( currentTick + anchorSpacing ) ;
2024-08-15 15:17:44 +02:00
uint160 sqrtRatioX96 = TickMath . getSqrtRatioAtTick ( currentTick ) ;
uint160 sqrtRatioAX96 = TickMath . getSqrtRatioAtTick ( tickLower ) ;
uint160 sqrtRatioBX96 = TickMath . getSqrtRatioAtTick ( tickUpper ) ;
uint256 anchorEthBalance = ethBalance - floorEthBalance ;
uint128 anchorLiquidity ;
if ( token0isWeth ) {
2025-07-08 10:31:41 +02:00
anchorLiquidity = LiquidityAmounts . getLiquidityForAmount0 ( sqrtRatioX96 , sqrtRatioBX96 , anchorEthBalance ) ;
2024-08-17 10:54:43 +03:00
pulledHarb = LiquidityAmounts . getAmount1ForLiquidity ( sqrtRatioAX96 , sqrtRatioX96 , anchorLiquidity ) ;
2024-08-15 15:17:44 +02:00
} else {
2025-07-08 10:31:41 +02:00
anchorLiquidity = LiquidityAmounts . getLiquidityForAmount1 ( sqrtRatioAX96 , sqrtRatioX96 , anchorEthBalance ) ;
2024-08-17 10:54:43 +03:00
pulledHarb = LiquidityAmounts . getAmount0ForLiquidity ( sqrtRatioX96 , sqrtRatioBX96 , anchorLiquidity ) ;
2024-08-15 15:17:44 +02:00
}
_mint ( Stage . ANCHOR , tickLower , tickUpper , anchorLiquidity ) ;
}
currentTick = currentTick / TICK_SPACING * TICK_SPACING ;
// set Discovery position
2024-08-21 12:37:17 +02:00
uint256 discoveryAmount ;
2024-08-15 15:17:44 +02:00
{
2025-07-08 10:31:41 +02:00
int24 tickLower = _clampToTickSpacing (
token0isWeth ? currentTick - DISCOVERY_SPACING - anchorSpacing : currentTick + anchorSpacing
) ;
int24 tickUpper = _clampToTickSpacing (
token0isWeth ? currentTick - anchorSpacing : currentTick + DISCOVERY_SPACING + anchorSpacing
) ;
2024-08-15 15:17:44 +02:00
uint160 sqrtRatioAX96 = TickMath . getSqrtRatioAtTick ( tickLower ) ;
uint160 sqrtRatioBX96 = TickMath . getSqrtRatioAtTick ( tickUpper ) ;
2025-07-08 10:31:41 +02:00
discoveryDepth = MIN_DISCOVERY_DEPTH + ( 4 * discoveryDepth * MIN_DISCOVERY_DEPTH / 10 ** 18 ) ;
discoveryAmount =
pulledHarb * uint24 ( DISCOVERY_SPACING ) * uint24 ( discoveryDepth ) / uint24 ( anchorSpacing ) / 100 ;
2024-08-15 15:17:44 +02:00
uint128 liquidity ;
if ( token0isWeth ) {
2025-07-08 10:31:41 +02:00
liquidity = LiquidityAmounts . getLiquidityForAmount1 ( sqrtRatioAX96 , sqrtRatioBX96 , discoveryAmount ) ;
2024-08-15 15:17:44 +02:00
} else {
2025-07-08 10:31:41 +02:00
liquidity = LiquidityAmounts . getLiquidityForAmount0 ( sqrtRatioAX96 , sqrtRatioBX96 , discoveryAmount ) ;
2024-08-15 15:17:44 +02:00
}
_mint ( Stage . DISCOVERY , tickLower , tickUpper , liquidity ) ;
harb . burn ( harb . balanceOf ( address ( this ) ) ) ;
}
2025-07-08 10:31:41 +02:00
2024-08-15 15:17:44 +02:00
// set Floor position
2024-07-06 18:36:13 +02:00
{
2025-02-01 21:49:15 +01:00
int24 vwapTick ;
uint256 outstandingSupply = harb . outstandingSupply ( ) ;
2024-08-21 12:37:17 +02:00
outstandingSupply -= pulledHarb ;
outstandingSupply -= ( outstandingSupply >= discoveryAmount ) ? discoveryAmount : outstandingSupply ;
2025-07-08 10:31:41 +02:00
uint256 vwapX96 = getAdjustedVWAP ( capitalInefficiency ) ;
2024-07-06 18:36:13 +02:00
uint256 requiredEthForBuyback = 0 ;
2025-07-08 10:31:41 +02:00
if ( vwapX96 > 0 ) {
2024-08-12 19:07:07 +02:00
requiredEthForBuyback = outstandingSupply . mulDiv ( vwapX96 , ( 1 << 96 ) ) ;
2024-07-06 18:36:13 +02:00
}
2025-07-08 10:31:41 +02:00
// make a new calculation of the vwapTick, having updated outstandingSupply
2024-07-16 19:47:39 +02:00
if ( floorEthBalance < requiredEthForBuyback ) {
2024-07-06 18:36:13 +02:00
// not enough ETH, find a lower price
2024-07-16 19:47:39 +02:00
requiredEthForBuyback = floorEthBalance ;
2025-07-08 10:31:41 +02:00
uint256 balancedCapital =
( 7 * outstandingSupply / 10 ) + ( outstandingSupply * capitalInefficiency / 10 ** 18 ) ;
vwapTick = tickAtPrice ( token0isWeth , balancedCapital , requiredEthForBuyback ) ;
2025-02-01 21:49:15 +01:00
emit EthScarcity ( currentTick , ethBalance , outstandingSupply , vwapX96 , vwapTick ) ;
2024-08-12 19:07:07 +02:00
} else if ( vwapX96 == 0 ) {
2024-07-16 19:47:39 +02:00
requiredEthForBuyback = floorEthBalance ;
2024-07-06 18:36:13 +02:00
vwapTick = currentTick ;
} else {
2024-08-13 17:06:12 +03:00
// ETH/HARB tick
vwapTick = tickAtPriceRatio ( int128 ( int256 ( vwapX96 >> 32 ) ) ) ;
// convert to pool tick
vwapTick = token0isWeth ? - vwapTick : vwapTick ;
2025-02-01 21:49:15 +01:00
emit EthAbundance ( currentTick , ethBalance , outstandingSupply , vwapX96 , vwapTick ) ;
2024-07-16 19:47:39 +02:00
}
2024-07-06 18:36:13 +02:00
// move floor below anchor, if needed
if ( token0isWeth ) {
2025-02-01 21:49:15 +01:00
vwapTick = ( vwapTick < currentTick + anchorSpacing ) ? currentTick + anchorSpacing : vwapTick ;
2024-07-06 18:36:13 +02:00
} else {
2025-02-01 21:49:15 +01:00
vwapTick = ( vwapTick > currentTick - anchorSpacing ) ? currentTick - anchorSpacing : vwapTick ;
2024-07-06 18:36:13 +02:00
}
2024-08-13 17:06:12 +03:00
// normalize tick position for pool
2025-07-06 10:08:59 +02:00
vwapTick = _clampToTickSpacing ( vwapTick ) ;
2024-07-06 18:36:13 +02:00
// calculate liquidity
uint160 sqrtRatioAX96 = TickMath . getSqrtRatioAtTick ( vwapTick ) ;
2025-07-08 10:31:41 +02:00
int24 floorTick = _clampToTickSpacing ( token0isWeth ? vwapTick + TICK_SPACING : vwapTick - TICK_SPACING ) ;
2024-07-06 18:36:13 +02:00
uint160 sqrtRatioBX96 = TickMath . getSqrtRatioAtTick ( floorTick ) ;
2024-08-15 15:17:44 +02:00
2024-08-16 12:00:13 +02:00
floorEthBalance = ( address ( this ) . balance + weth . balanceOf ( address ( this ) ) ) ;
2024-08-15 15:17:44 +02:00
2024-07-16 19:47:39 +02:00
uint128 liquidity ;
if ( token0isWeth ) {
2025-07-08 10:31:41 +02:00
liquidity = LiquidityAmounts . getLiquidityForAmount0 ( sqrtRatioAX96 , sqrtRatioBX96 , floorEthBalance ) ;
2024-07-16 19:47:39 +02:00
} else {
2025-07-08 10:31:41 +02:00
liquidity = LiquidityAmounts . getLiquidityForAmount1 ( sqrtRatioAX96 , sqrtRatioBX96 , floorEthBalance ) ;
2024-07-16 19:47:39 +02:00
}
2024-07-06 18:36:13 +02:00
_mint ( Stage . FLOOR , token0isWeth ? vwapTick : floorTick , token0isWeth ? floorTick : vwapTick , liquidity ) ;
}
2024-07-18 16:50:23 +02:00
}
2024-07-06 18:36:13 +02:00
function _scrape ( ) internal {
2024-06-09 16:06:41 +02:00
uint256 fee0 = 0 ;
uint256 fee1 = 0 ;
2024-07-06 18:36:13 +02:00
uint256 currentPrice ;
2025-07-08 10:31:41 +02:00
for ( uint256 i = uint256 ( Stage . FLOOR ) ; i <= uint256 ( Stage . DISCOVERY ) ; i ++ ) {
2024-06-09 16:06:41 +02:00
TokenPosition storage position = positions [ Stage ( i ) ] ;
if ( position . liquidity > 0 ) {
2025-07-08 10:31:41 +02:00
( uint256 amount0 , uint256 amount1 ) =
pool . burn ( position . tickLower , position . tickUpper , position . liquidity ) ;
2024-06-09 16:06:41 +02:00
// Collect the maximum possible amounts which include fees
( uint256 collected0 , uint256 collected1 ) = pool . collect (
address ( this ) ,
position . tickLower ,
position . tickUpper ,
2025-07-08 10:31:41 +02:00
type ( uint128 ) . max , // Collect the max uint128 value, effectively trying to collect all
2024-06-09 16:06:41 +02:00
type ( uint128 ) . max
) ;
// Calculate the fees
fee0 += collected0 - amount0 ;
fee1 += collected1 - amount1 ;
if ( i == uint256 ( Stage . ANCHOR ) ) {
2024-07-18 16:50:23 +02:00
// the historic archor position is only an approximation for the price
2025-02-01 21:49:15 +01:00
int24 tick = position . tickLower + ( position . tickUpper - position . tickLower / 2 ) ;
currentPrice = priceAtTick ( token0isWeth ? - 1 * tick : tick ) ;
2024-06-09 16:06:41 +02:00
}
}
}
2024-07-06 18:36:13 +02:00
2024-06-09 16:06:41 +02:00
// Transfer fees to the fee destination
2024-07-06 18:36:13 +02:00
// and record transaction totals
2024-06-09 16:06:41 +02:00
if ( fee0 > 0 ) {
2024-07-06 18:36:13 +02:00
if ( token0isWeth ) {
IERC20 ( address ( weth ) ) . transfer ( feeDestination , fee0 ) ;
2024-07-18 16:50:23 +02:00
_recordVolumeAndPrice ( currentPrice , fee0 ) ;
2024-07-06 18:36:13 +02:00
} else {
IERC20 ( address ( harb ) ) . transfer ( feeDestination , fee0 ) ;
}
2024-06-09 16:06:41 +02:00
}
if ( fee1 > 0 ) {
2024-07-06 18:36:13 +02:00
if ( token0isWeth ) {
IERC20 ( address ( harb ) ) . transfer ( feeDestination , fee1 ) ;
} else {
IERC20 ( address ( weth ) ) . transfer ( feeDestination , fee1 ) ;
2024-07-18 16:50:23 +02:00
_recordVolumeAndPrice ( currentPrice , fee1 ) ;
2024-07-06 18:36:13 +02:00
}
2024-06-09 16:06:41 +02:00
}
}
2024-06-07 12:33:20 +02:00
function _isPriceStable ( int24 currentTick ) internal view returns ( bool ) {
2024-06-07 11:22:22 +02:00
uint32 [ ] memory secondsAgo = new uint32 [ ] ( 2 ) ;
2024-07-18 16:50:23 +02:00
secondsAgo [ 0 ] = PRICE_STABILITY_INTERVAL ; // 5 minutes ago
2025-07-08 10:31:41 +02:00
secondsAgo [ 1 ] = 0 ; // current block timestamp
2024-06-07 11:22:22 +02:00
2024-07-09 18:00:39 +02:00
int56 tickCumulativeDiff ;
int24 averageTick ;
try pool . observe ( secondsAgo ) returns ( int56 [ ] memory tickCumulatives , uint160 [ ] memory ) {
tickCumulativeDiff = tickCumulatives [ 1 ] - tickCumulatives [ 0 ] ;
2024-07-18 16:50:23 +02:00
averageTick = int24 ( tickCumulativeDiff / int56 ( int32 ( PRICE_STABILITY_INTERVAL ) ) ) ;
2024-07-09 18:00:39 +02:00
} catch {
2024-07-18 16:50:23 +02:00
// try with a higher timeframe
secondsAgo [ 0 ] = PRICE_STABILITY_INTERVAL * 200 ;
2025-07-08 10:31:41 +02:00
( int56 [ ] memory tickCumulatives , ) = pool . observe ( secondsAgo ) ;
2024-07-18 16:50:23 +02:00
tickCumulativeDiff = tickCumulatives [ 1 ] - tickCumulatives [ 0 ] ;
averageTick = int24 ( tickCumulativeDiff / int56 ( int32 ( PRICE_STABILITY_INTERVAL ) ) ) ;
2024-07-09 18:00:39 +02:00
}
2024-06-07 11:22:22 +02:00
return ( currentTick >= averageTick - MAX_TICK_DEVIATION && currentTick <= averageTick + MAX_TICK_DEVIATION ) ;
}
2024-07-18 07:35:39 +02:00
/// @notice Adjusts liquidity positions in response to an increase or decrease in the Harberg token's price.
2024-07-16 20:47:06 +02:00
/// @dev This function should be called when significant price movement is detected. It recalibrates the liquidity ranges to align with the new market conditions.
2025-02-01 21:49:15 +01:00
function recenter ( ) external returns ( bool isUp ) {
2025-01-23 13:21:49 +01:00
// Fetch the current tick from the Uniswap V3 pool
2025-07-08 10:31:41 +02:00
( , int24 currentTick , , , , , ) = pool . slot0 ( ) ;
2025-01-23 13:21:49 +01:00
2024-09-12 17:00:24 +02:00
if ( recenterAccess != address ( 0 ) ) {
require ( msg . sender == recenterAccess , " access denied " ) ;
2025-01-23 13:21:49 +01:00
} else {
// check slippage with oracle
require ( _isPriceStable ( currentTick ) , " price deviated from oracle " ) ;
2024-09-12 17:00:24 +02:00
}
2024-04-03 21:43:12 +02:00
2024-11-07 15:33:40 +00:00
isUp = false ;
2024-07-16 20:47:06 +02:00
// check how price moved
2024-04-03 21:43:12 +02:00
if ( positions [ Stage . ANCHOR ] . liquidity > 0 ) {
2024-07-16 20:47:06 +02:00
// get the anchor position
2024-04-03 21:43:12 +02:00
int24 anchorTickLower = positions [ Stage . ANCHOR ] . tickLower ;
int24 anchorTickUpper = positions [ Stage . ANCHOR ] . tickUpper ;
2024-06-09 16:06:41 +02:00
2024-04-03 21:43:12 +02:00
// center tick can be calculated positive and negative numbers the same
2025-02-01 21:49:15 +01:00
int24 centerTick = anchorTickLower + ( anchorTickUpper - anchorTickLower ) ;
2024-09-10 19:13:43 +02:00
uint256 minAmplitude = uint24 ( TICK_SPACING ) * 2 ;
2024-04-03 21:43:12 +02:00
// Determine the correct comparison direction based on token0isWeth
2024-07-16 20:47:06 +02:00
isUp = token0isWeth ? currentTick < centerTick : currentTick > centerTick ;
2024-04-29 06:27:28 +02:00
bool isEnough = SignedMath . abs ( currentTick - centerTick ) > minAmplitude ;
2024-04-03 21:43:12 +02:00
// Check Conditions
2024-07-16 20:47:06 +02:00
require ( isEnough , " amplitude not reached. " ) ;
2024-04-03 21:43:12 +02:00
}
2024-07-16 20:47:06 +02:00
// take out all old positions
2024-06-09 16:06:41 +02:00
_scrape ( ) ;
2024-07-16 20:47:06 +02:00
if ( isUp ) {
harb . setPreviousTotalSupply ( harb . totalSupply ( ) ) ;
}
2025-07-08 10:31:41 +02:00
try optimizer . getLiquidityParams ( ) returns (
uint256 capitalInefficiency , uint256 anchorShare , uint24 anchorWidth , uint256 discoveryDepth
) {
capitalInefficiency = ( capitalInefficiency > 10 ** 18 ) ? 10 ** 18 : capitalInefficiency ;
anchorShare = ( anchorShare > 10 ** 18 ) ? 10 ** 18 : anchorShare ;
2025-02-01 21:49:15 +01:00
anchorWidth = ( anchorWidth > 100 ) ? 100 : anchorWidth ;
2025-07-08 10:31:41 +02:00
discoveryDepth = ( discoveryDepth > 10 ** 18 ) ? 10 ** 18 : discoveryDepth ;
2025-02-01 21:49:15 +01:00
// set new positions
_set ( currentTick , capitalInefficiency , anchorShare , anchorWidth , discoveryDepth ) ;
2024-11-07 15:33:40 +00:00
} catch {
2025-02-01 21:49:15 +01:00
// set new positions with default, average parameters
2025-07-08 10:31:41 +02:00
_set ( currentTick , 5 * 10 ** 17 , 5 * 10 ** 17 , 5 * 10 , 5 * 10 ** 17 ) ;
2024-11-07 15:33:40 +00:00
}
2024-03-18 12:42:30 +01:00
}
}