harb/onchain/src/LiquidityManager.sol

481 lines
23 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: GPL-3.0-or-later
2024-03-28 19:55:01 +01:00
pragma solidity ^0.8.19;
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";
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";
import "./interfaces/IWETH9.sol";
2024-07-18 07:35:39 +02:00
import {Harberg} from "./Harberg.sol";
import {Sentimenter} from "./Sentimenter.sol";
2024-03-28 21:50:22 +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.
* @dev Utilizes Uniswap V3's concentrated liquidity feature, enabling highly efficient use of capital.
2024-07-13 18:33:47 +02:00
*/
contract LiquidityManager {
2024-08-12 19:07:07 +02:00
using Math for uint256;
2024-07-18 07:35:39 +02:00
// State variables to track total ETH spent
2024-08-12 19:07:07 +02:00
uint256 public cumulativeVolumeWeightedPriceX96;
2024-07-18 07:35:39 +02:00
uint256 public cumulativeVolume;
2024-07-16 19:47:39 +02:00
// the minimum granularity of liquidity positions in the Uniswap V3 pool. this is a 1% pool.
2024-07-09 18:00:39 +02:00
int24 internal constant TICK_SPACING = 200;
2024-07-16 19:47:39 +02:00
// defines the width of the anchor position from the current price to discovery position.
2024-07-09 18:00:39 +02:00
int24 internal constant ANCHOR_SPACING = 5 * TICK_SPACING;
2024-07-16 19:47:39 +02:00
// DISCOVERY_SPACING determines the range above the current price where new tokens are minted and sold.
// 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
uint128 internal constant 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
int24 internal constant MAX_TICK_DEVIATION = 50; // how much is that?
2024-07-16 19:47:39 +02: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;
Sentimenter private immutable sentimenter;
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;
address private recenterAccess;
2024-07-16 20:47:06 +02:00
// the 3 positions this contract is managing
enum Stage { FLOOR, ANCHOR, DISCOVERY }
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-07-13 14:56:13 +02:00
error ZeroAddressInSetter();
error AddressAlreadySet();
event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 sentiment, int24 vwapTick);
event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 sentiment, 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-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.
constructor(address _factory, address _WETH9, address _harb, address _sentimenter) {
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);
token0isWeth = _WETH9 < _harb;
sentimenter = Sentimenter(_sentimenter);
}
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.
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;
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
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-07-17 14:08:53 +02:00
function setMinStakeSupplyFraction(uint256 mssf_) external onlyFeeDestination {
harb.setMinStakeSupplyFraction(mssf_);
}
function setRecenterAccess(address addr) external onlyFeeDestination {
recenterAccess = addr;
}
function revokeRecenterAccess() external onlyFeeDestination {
recenterAccess = address(0);
}
2024-04-11 07:28:54 +02:00
receive() external payable {
}
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) {
// 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.
2024-08-12 19:07:07 +02:00
int128 priceRatioX64 = ABDKMath64x64.div(
2024-04-11 07:28:54 +02:00
int128(int256(tokenAmount)),
int128(int256(ethAmount))
);
// HARB/ETH
tick_ = tickAtPriceRatio(priceRatioX64);
2024-04-11 07:28:54 +02:00
}
// convert to tick in a pool
tick_ = t0isWeth ? tick_ : -tick_;
2024-08-12 19:07:07 +02: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.
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
pool.mint(
address(this),
tickLower,
tickUpper,
liquidity,
abi.encode(poolKey)
);
// put into storage
2024-04-27 07:04:33 +02:00
positions[stage] = TokenPosition({
2024-03-28 21:50:22 +01:00
liquidity: liquidity,
tickLower: tickLower,
2024-06-09 16:06:41 +02:00
tickUpper: tickUpper
2024-03-28 21:50:22 +01:00
});
}
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.
function _set(int24 currentTick, uint256 sentiment) internal {
2024-07-06 18:36:13 +02:00
// estimate the lower tick of the anchor
2024-07-06 18:36:13 +02:00
int24 vwapTick;
uint256 outstandingSupply = harb.outstandingSupply();
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
// this enforces an floor liquidity share of 75% to 95 %;
uint256 floorEthBalance = (3 * ethBalance / 4) + (2 * sentiment * ethBalance / 10**19);
if (outstandingSupply > 0) {
// this enables a "capital inefficiency" of 70% to 170%;
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * sentiment / 10**18);
vwapTick = tickAtPrice(token0isWeth, balancedCapital, floorEthBalance);
} else {
vwapTick = token0isWeth ? currentTick + ANCHOR_SPACING : currentTick - ANCHOR_SPACING;
}
// move vwapTick below currentTick, if needed
if (token0isWeth) {
vwapTick = (vwapTick < currentTick + ANCHOR_SPACING) ? currentTick + ANCHOR_SPACING : vwapTick;
} else {
vwapTick = (vwapTick > currentTick - ANCHOR_SPACING) ? currentTick - ANCHOR_SPACING : vwapTick;
}
// set Anchor position
2024-08-16 12:00:13 +02:00
uint256 pulledHarb;
{
int24 tickLower = token0isWeth ? currentTick - ANCHOR_SPACING : vwapTick;
int24 tickUpper = token0isWeth ? vwapTick : currentTick + ANCHOR_SPACING;
tickLower = tickLower / TICK_SPACING * TICK_SPACING;
tickUpper = tickUpper / TICK_SPACING * TICK_SPACING;
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
uint256 anchorEthBalance = ethBalance - floorEthBalance;
uint128 anchorLiquidity;
if (token0isWeth) {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioX96, sqrtRatioBX96, anchorEthBalance
);
pulledHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, anchorLiquidity);
} else {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioX96, anchorEthBalance
);
pulledHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, anchorLiquidity);
}
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
}
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
// set Discovery position
uint256 discoveryAmount;
{
int24 tickLower = token0isWeth ? currentTick - DISCOVERY_SPACING - ANCHOR_SPACING : currentTick + ANCHOR_SPACING;
int24 tickUpper = token0isWeth ? currentTick - ANCHOR_SPACING : currentTick + DISCOVERY_SPACING + ANCHOR_SPACING;
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
discoveryAmount = pulledHarb * uint24(DISCOVERY_SPACING) * uint24(DISCOVERY_DEPTH) / uint24(ANCHOR_SPACING) / 100;
uint128 liquidity;
if (token0isWeth) {
liquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioBX96, discoveryAmount
);
} else {
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, discoveryAmount
);
}
_mint(Stage.DISCOVERY, tickLower, tickUpper, liquidity);
harb.burn(harb.balanceOf(address(this)));
}
// set Floor position
2024-07-06 18:36:13 +02:00
{
outstandingSupply = harb.outstandingSupply();
outstandingSupply -= pulledHarb;
outstandingSupply -= (outstandingSupply >= discoveryAmount) ? discoveryAmount : outstandingSupply;
2024-08-12 19:07:07 +02:00
uint256 vwapX96 = 0;
2024-07-06 18:36:13 +02:00
uint256 requiredEthForBuyback = 0;
if (cumulativeVolume > 0) {
vwapX96 = cumulativeVolumeWeightedPriceX96 / cumulativeVolume; // in harb/eth
vwapX96 = (7 * vwapX96 / 10) + (vwapX96 * sentiment / 10**18);
2024-08-12 19:07:07 +02:00
requiredEthForBuyback = outstandingSupply.mulDiv(vwapX96, (1 << 96));
2024-07-06 18:36:13 +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;
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * sentiment / 10**18);
vwapTick = tickAtPrice(token0isWeth, balancedCapital , requiredEthForBuyback);
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, sentiment, 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 {
// ETH/HARB tick
vwapTick = tickAtPriceRatio(int128(int256(vwapX96 >> 32)));
// convert to pool tick
vwapTick = token0isWeth ? -vwapTick : vwapTick;
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, sentiment, 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) {
vwapTick = (vwapTick < currentTick + ANCHOR_SPACING) ? currentTick + ANCHOR_SPACING : vwapTick;
2024-07-06 18:36:13 +02:00
} else {
vwapTick = (vwapTick > currentTick - ANCHOR_SPACING) ? currentTick - ANCHOR_SPACING : vwapTick;
2024-07-06 18:36:13 +02:00
}
// normalize tick position for pool
2024-07-06 18:36:13 +02:00
vwapTick = vwapTick / TICK_SPACING * TICK_SPACING;
// calculate liquidity
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(vwapTick);
2024-07-09 18:00:39 +02:00
int24 floorTick = token0isWeth ? vwapTick + TICK_SPACING: vwapTick - TICK_SPACING;
2024-07-06 18:36:13 +02:00
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorTick);
2024-08-16 12:00:13 +02:00
floorEthBalance = (address(this).balance + weth.balanceOf(address(this)));
2024-07-16 19:47:39 +02:00
uint128 liquidity;
if (token0isWeth) {
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, floorEthBalance
2024-07-16 19:47:39 +02:00
);
} else {
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-04-03 21:43:12 +02:00
}
2024-08-12 19:07:07 +02:00
function _recordVolumeAndPrice(uint256 currentPriceX96, uint256 fee) internal {
2024-07-18 16:50:23 +02:00
// assuming FEE is 1%
uint256 volume = fee * 100;
2024-08-12 19:07:07 +02:00
uint256 volumeWeightedPriceX96 = currentPriceX96 * volume;
2024-07-18 16:50:23 +02:00
// Check for potential overflow. 10**70 is close to 2^256
2024-08-12 19:07:07 +02:00
if (cumulativeVolumeWeightedPriceX96 > 10**70) {
2024-07-18 16:50:23 +02:00
uint256 zipFactor = 10**35;
uint256 desiredPrecision = 10**5;
while (zipFactor * desiredPrecision > cumulativeVolume) {
zipFactor /= desiredPrecision;
}
// Handle overflow: zip historic trade data
2024-08-12 19:07:07 +02:00
cumulativeVolumeWeightedPriceX96 = cumulativeVolumeWeightedPriceX96 / zipFactor;
2024-07-18 16:50:23 +02:00
// cumulativeVolume should be well higer than zipFactor
cumulativeVolume = cumulativeVolume / zipFactor;
}
2024-08-12 19:07:07 +02:00
cumulativeVolumeWeightedPriceX96 += volumeWeightedPriceX96;
2024-07-18 16:50:23 +02:00
cumulativeVolume += volume;
}
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;
2024-06-09 16:06:41 +02:00
for (uint256 i=uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
TokenPosition storage position = positions[Stage(i)];
if (position.liquidity > 0) {
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
// Collect the maximum possible amounts which include fees
(uint256 collected0, uint256 collected1) = pool.collect(
address(this),
position.tickLower,
position.tickUpper,
type(uint128).max, // Collect the max uint128 value, effectively trying to collect all
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
2024-07-09 18:00:39 +02:00
int24 tick = token0isWeth ? -1 * (position.tickLower + ANCHOR_SPACING): position.tickUpper - ANCHOR_SPACING;
2024-08-12 19:07:07 +02:00
currentPrice = priceAtTick(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
2024-06-07 11:22:22 +02:00
secondsAgo[1] = 0; // current block timestamp
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;
(int56[] memory tickCumulatives, ) = pool.observe(secondsAgo);
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.
function recenter() external returns (bool isUp, uint256 sentiment) {
if (recenterAccess != address(0)) {
require(msg.sender == recenterAccess, "access denied");
}
2024-04-03 21:43:12 +02:00
// Fetch the current tick from the Uniswap V3 pool
2024-07-16 19:47:39 +02:00
(, int24 currentTick, , , , , ) = pool.slot0();
2024-06-07 11:22:22 +02:00
// check slippage with oracle
2024-06-07 12:33:20 +02:00
require(_isPriceStable(currentTick), "price deviated from oracle");
2024-04-03 21:43:12 +02: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
2024-07-09 18:00:39 +02:00
int24 centerTick = token0isWeth ? anchorTickLower + ANCHOR_SPACING : anchorTickUpper - ANCHOR_SPACING;
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());
}
try sentimenter.getSentiment() returns (uint256 currentSentiment) {
sentiment = (currentSentiment > 10**18) ? 10**18 : currentSentiment;
} catch {
//sentiment = 10**18 / 2;
sentiment = 0;
}
2024-07-16 20:47:06 +02:00
// set new positions
_set(currentTick, sentiment);
}
}