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-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";
|
|
|
|
|
import {Harb} from "./Harb.sol";
|
|
|
|
|
|
2024-03-28 21:50:22 +01:00
|
|
|
|
2024-03-18 12:42:30 +01:00
|
|
|
/**
|
2024-07-04 10:24:06 +02:00
|
|
|
* @title LiquidityManager - A contract that implements an automated market making strategy.
|
|
|
|
|
* It maintains 3 positions:
|
|
|
|
|
* - The floor position guarantees the capacity needed to maintain a minimum price of the HARB token It is a very tight liquidity range with enough reserve assets to buy back the circulating supply.
|
|
|
|
|
* - The anchor range provides liquidity around the current market price, ensuring liquid trading conditions for the token, regardless of the market environment.
|
2024-07-13 14:56:13 +02:00
|
|
|
* - The discovery range starts 1000 ticks above the current market price and increases from there. It consists solely of unissued tokens, which are sold as the market price increases.
|
2024-07-04 10:24:06 +02:00
|
|
|
* The liquidity surplus obtained from selling tokens in the discovery range is directed back into the floor and anchor positions.
|
|
|
|
|
*/
|
2024-03-18 12:42:30 +01:00
|
|
|
contract BaseLineLP {
|
2024-07-09 18:00:39 +02:00
|
|
|
int24 internal constant TICK_SPACING = 200;
|
|
|
|
|
int24 internal constant ANCHOR_SPACING = 5 * TICK_SPACING;
|
|
|
|
|
int24 internal constant DISCOVERY_SPACING = 11000;
|
|
|
|
|
int24 internal constant MAX_TICK_DEVIATION = 50; // how much is that?
|
2024-03-18 12:42:30 +01:00
|
|
|
// default fee of 1%
|
2024-07-09 18:00:39 +02:00
|
|
|
uint24 internal constant FEE = uint24(10_000);
|
|
|
|
|
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
|
|
|
|
|
uint256 internal constant ANCHOR_LIQ_SHARE = 5; // 5%
|
|
|
|
|
uint256 internal constant CAPITAL_INEFFICIENCY = 120; // 20%
|
2024-03-28 19:55:01 +01:00
|
|
|
|
|
|
|
|
enum Stage { FLOOR, ANCHOR, DISCOVERY }
|
|
|
|
|
|
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;
|
|
|
|
|
Harb private immutable harb;
|
|
|
|
|
IUniswapV3Pool private immutable pool;
|
|
|
|
|
bool private immutable token0isWeth;
|
2024-07-09 18:00:39 +02:00
|
|
|
PoolKey private poolKey;
|
2024-03-18 12:42:30 +01:00
|
|
|
|
|
|
|
|
struct TokenPosition {
|
|
|
|
|
// the liquidity of the position
|
|
|
|
|
uint128 liquidity;
|
|
|
|
|
int24 tickLower;
|
|
|
|
|
int24 tickUpper;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-06 18:36:13 +02:00
|
|
|
// State variables to track total ETH spent
|
|
|
|
|
uint256 public cumulativeVolumeWeightedPrice;
|
|
|
|
|
uint256 public cumulativeVolume;
|
2024-05-29 17:26:19 +02:00
|
|
|
mapping(Stage => TokenPosition) public positions;
|
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();
|
|
|
|
|
|
2024-07-09 18:00:39 +02:00
|
|
|
// TODO: add events
|
2024-03-18 12:42:30 +01:00
|
|
|
|
|
|
|
|
constructor(address _factory, address _WETH9, address _harb) {
|
|
|
|
|
factory = _factory;
|
|
|
|
|
weth = IWETH9(_WETH9);
|
|
|
|
|
poolKey = PoolAddress.getPoolKey(_WETH9, _harb, FEE);
|
|
|
|
|
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
|
|
|
|
|
harb = Harb(_harb);
|
|
|
|
|
token0isWeth = _WETH9 < _harb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
harb.mint(token0isWeth ? amount1Owed : amount0Owed);
|
|
|
|
|
// 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-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-06-25 09:38:23 +02:00
|
|
|
//TODO: what to do with stuck funds if slide/shift become inoperable?
|
2024-04-11 07:28:54 +02:00
|
|
|
receive() external payable {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
uint160 sqrtPriceX96;
|
|
|
|
|
if (tokenAmount == 0) {
|
|
|
|
|
sqrtPriceX96 = MIN_SQRT_RATIO;
|
|
|
|
|
} 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.
|
|
|
|
|
int128 priceRatio = ABDKMath64x64.div(
|
|
|
|
|
int128(int256(tokenAmount)),
|
|
|
|
|
int128(int256(ethAmount))
|
|
|
|
|
);
|
|
|
|
|
// Convert the price ratio into a sqrt price in the format expected by Uniswap's TickMath.
|
|
|
|
|
sqrtPriceX96 = uint160(
|
|
|
|
|
int160(ABDKMath64x64.sqrt(priceRatio) << 32)
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-03-28 21:50:22 +01:00
|
|
|
tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
|
2024-07-09 18:00:39 +02:00
|
|
|
tick_ = t0isWeth ? tick_ : -tick_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function tickToPrice(int24 tick) public pure returns (uint256 priceRatio) {
|
|
|
|
|
uint160 sqrtRatio = TickMath.getSqrtRatioAtTick(tick);
|
|
|
|
|
uint256 adjustedSqrtRatio = uint256(sqrtRatio) / (1 << 48);
|
|
|
|
|
priceRatio = adjustedSqrtRatio * adjustedSqrtRatio;
|
2024-03-28 19:55:01 +01:00
|
|
|
}
|
|
|
|
|
|
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-06 18:36:13 +02:00
|
|
|
function _set(uint160 sqrtPriceX96, int24 currentTick) internal {
|
|
|
|
|
|
|
|
|
|
// ### set Floor position
|
|
|
|
|
int24 vwapTick;
|
|
|
|
|
{
|
2024-07-13 14:56:13 +02:00
|
|
|
uint256 outstandingSupply = harb.outstandingSupply();
|
2024-07-06 18:36:13 +02:00
|
|
|
uint256 vwap = 0;
|
|
|
|
|
uint256 requiredEthForBuyback = 0;
|
|
|
|
|
if (cumulativeVolume > 0) {
|
|
|
|
|
vwap = cumulativeVolumeWeightedPrice / cumulativeVolume;
|
2024-07-09 18:00:39 +02:00
|
|
|
requiredEthForBuyback = outstandingSupply * 10**18 / vwap;
|
2024-07-06 18:36:13 +02:00
|
|
|
}
|
|
|
|
|
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
|
2024-07-09 18:00:39 +02:00
|
|
|
// leave at least ANCHOR_LIQ_SHARE% of supply for anchor
|
2024-07-06 19:25:09 +02:00
|
|
|
ethBalance = ethBalance * (100 - ANCHOR_LIQ_SHARE) / 100;
|
2024-07-06 18:36:13 +02:00
|
|
|
if (ethBalance < requiredEthForBuyback) {
|
|
|
|
|
// not enough ETH, find a lower price
|
|
|
|
|
requiredEthForBuyback = ethBalance;
|
2024-07-09 18:00:39 +02:00
|
|
|
outstandingSupply = outstandingSupply * CAPITAL_INEFFICIENCY / 100;
|
|
|
|
|
vwapTick = tickAtPrice(token0isWeth, outstandingSupply , requiredEthForBuyback);
|
2024-07-06 18:36:13 +02:00
|
|
|
} else if (vwap == 0) {
|
|
|
|
|
requiredEthForBuyback = ethBalance;
|
|
|
|
|
vwapTick = currentTick;
|
|
|
|
|
} else {
|
2024-07-09 18:00:39 +02:00
|
|
|
vwap = cumulativeVolumeWeightedPrice * CAPITAL_INEFFICIENCY / 100 / cumulativeVolume; // in harb/eth
|
|
|
|
|
vwapTick = tickAtPrice(token0isWeth, token0isWeth ? vwap : 10**18, token0isWeth ? 10**18 : vwap);
|
2024-07-13 14:56:13 +02:00
|
|
|
vwapTick = token0isWeth ? vwapTick : -vwapTick;
|
2024-07-06 18:36:13 +02:00
|
|
|
if (requiredEthForBuyback < ethBalance) {
|
|
|
|
|
// invest a majority of the ETH still in floor, even though not needed
|
|
|
|
|
requiredEthForBuyback = (requiredEthForBuyback + (5 * ethBalance)) / 6;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// move floor below anchor, if needed
|
|
|
|
|
if (token0isWeth) {
|
|
|
|
|
vwapTick = (vwapTick < currentTick + ANCHOR_SPACING) ? currentTick + ANCHOR_SPACING : vwapTick;
|
|
|
|
|
} else {
|
|
|
|
|
vwapTick = (vwapTick > currentTick - ANCHOR_SPACING) ? currentTick - ANCHOR_SPACING : vwapTick;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// normalize tick position for pool
|
|
|
|
|
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);
|
|
|
|
|
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
|
|
|
|
sqrtPriceX96,
|
|
|
|
|
sqrtRatioAX96,
|
|
|
|
|
sqrtRatioBX96,
|
|
|
|
|
token0isWeth ? requiredEthForBuyback : 0,
|
|
|
|
|
token0isWeth ? 0 : requiredEthForBuyback
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// mint
|
|
|
|
|
_mint(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-28 21:50:22 +01:00
|
|
|
// ### set Anchor position
|
|
|
|
|
uint128 anchorLiquidity;
|
2024-03-18 12:42:30 +01:00
|
|
|
{
|
2024-07-06 18:36:13 +02:00
|
|
|
int24 tickLower = token0isWeth ? currentTick - ANCHOR_SPACING : vwapTick;
|
|
|
|
|
int24 tickUpper = token0isWeth ? vwapTick : currentTick + ANCHOR_SPACING;
|
2024-07-04 10:24:06 +02:00
|
|
|
tickLower = tickLower / TICK_SPACING * TICK_SPACING;
|
|
|
|
|
tickUpper = tickUpper / TICK_SPACING * TICK_SPACING;
|
2024-07-09 18:00:39 +02:00
|
|
|
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
|
|
|
|
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
|
|
|
|
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
|
|
|
|
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this))) * 98 / 100;
|
|
|
|
|
anchorLiquidity = LiquidityAmounts.getLiquidityForAmounts(
|
|
|
|
|
sqrtRatioX96,
|
|
|
|
|
sqrtRatioAX96,
|
|
|
|
|
sqrtRatioBX96,
|
|
|
|
|
token0isWeth ? ethBalance : 10**30,
|
|
|
|
|
token0isWeth ? 10**30: ethBalance
|
|
|
|
|
);
|
2024-07-06 18:36:13 +02:00
|
|
|
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
|
2024-03-18 12:42:30 +01:00
|
|
|
}
|
2024-07-04 10:24:06 +02:00
|
|
|
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
2024-03-18 12:42:30 +01:00
|
|
|
|
2024-03-28 21:50:22 +01:00
|
|
|
// ## set Discovery position
|
2024-03-18 12:42:30 +01:00
|
|
|
{
|
2024-04-27 07:04:33 +02:00
|
|
|
int24 tickLower = token0isWeth ? currentTick - DISCOVERY_SPACING - ANCHOR_SPACING : currentTick + ANCHOR_SPACING;
|
|
|
|
|
int24 tickUpper = token0isWeth ? currentTick - ANCHOR_SPACING : currentTick + DISCOVERY_SPACING + ANCHOR_SPACING;
|
2024-03-28 21:50:22 +01:00
|
|
|
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
|
|
|
|
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
2024-03-28 19:55:01 +01:00
|
|
|
// discovery with 1.5 times as much liquidity per tick as anchor
|
2024-03-28 21:50:22 +01:00
|
|
|
// A * 3 11000 A * 55
|
|
|
|
|
// D = ----- * ----- = ------
|
|
|
|
|
// 2 600 2
|
|
|
|
|
uint128 liquidity = anchorLiquidity * 55 / 2;
|
2024-03-28 19:55:01 +01:00
|
|
|
uint256 harbInDiscovery;
|
|
|
|
|
if (token0isWeth) {
|
2024-04-27 07:04:33 +02:00
|
|
|
harbInDiscovery = LiquidityAmounts.getAmount0ForLiquidity(
|
2024-03-28 19:55:01 +01:00
|
|
|
sqrtRatioAX96,
|
|
|
|
|
sqrtRatioBX96,
|
|
|
|
|
liquidity
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2024-04-27 07:04:33 +02:00
|
|
|
harbInDiscovery = LiquidityAmounts.getAmount1ForLiquidity(
|
2024-03-28 19:55:01 +01:00
|
|
|
sqrtRatioAX96,
|
|
|
|
|
sqrtRatioBX96,
|
|
|
|
|
liquidity
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-04-27 07:04:33 +02:00
|
|
|
harb.mint(harbInDiscovery);
|
|
|
|
|
_mint(Stage.DISCOVERY, tickLower, tickUpper, liquidity);
|
|
|
|
|
harb.burn(harb.balanceOf(address(this)));
|
2024-03-18 12:42:30 +01:00
|
|
|
}
|
2024-04-03 21:43:12 +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;
|
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-09 18:00:39 +02:00
|
|
|
int24 tick = token0isWeth ? -1 * (position.tickLower + ANCHOR_SPACING): position.tickUpper - ANCHOR_SPACING;
|
|
|
|
|
currentPrice = tickToPrice(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);
|
|
|
|
|
uint256 volume = fee0 * 100;
|
|
|
|
|
uint256 volumeWeightedPrice = currentPrice * volume;
|
|
|
|
|
cumulativeVolumeWeightedPrice += volumeWeightedPrice;
|
|
|
|
|
cumulativeVolume += volume;
|
|
|
|
|
} 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);
|
|
|
|
|
uint256 volume = fee1 * 100;
|
|
|
|
|
uint256 volumeWeightedPrice = currentPrice * volume;
|
|
|
|
|
cumulativeVolumeWeightedPrice += volumeWeightedPrice;
|
|
|
|
|
cumulativeVolume += volume;
|
|
|
|
|
}
|
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 timeInterval = 300; // 5 minutes in seconds
|
|
|
|
|
uint32[] memory secondsAgo = new uint32[](2);
|
|
|
|
|
secondsAgo[0] = timeInterval; // 5 minutes ago
|
|
|
|
|
secondsAgo[1] = 0; // current block timestamp
|
|
|
|
|
|
2024-07-09 18:00:39 +02:00
|
|
|
//(int56[] memory tickCumulatives,) = pool.observe(secondsAgo);
|
|
|
|
|
int56 tickCumulativeDiff;
|
|
|
|
|
int24 averageTick;
|
|
|
|
|
try pool.observe(secondsAgo) returns (int56[] memory tickCumulatives, uint160[] memory) {
|
|
|
|
|
tickCumulativeDiff = tickCumulatives[1] - tickCumulatives[0];
|
|
|
|
|
averageTick = int24(tickCumulativeDiff / int56(int32(timeInterval)));
|
|
|
|
|
// Process the data
|
|
|
|
|
} catch {
|
2024-07-13 14:56:13 +02:00
|
|
|
// TODO: Handle the error, possibly by trying with a different time interval or providing a default response
|
2024-07-09 18:00:39 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-07 11:22:22 +02:00
|
|
|
return (currentTick >= averageTick - MAX_TICK_DEVIATION && currentTick <= averageTick + MAX_TICK_DEVIATION);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-13 14:56:13 +02:00
|
|
|
// call this function when price has moved up x%
|
2024-06-13 10:50:09 +02:00
|
|
|
// TODO: write a bot that calls this function regularly
|
2024-04-03 21:43:12 +02:00
|
|
|
function shift() external {
|
|
|
|
|
require(positions[Stage.ANCHOR].liquidity > 0, "Not initialized");
|
|
|
|
|
// Fetch the current tick from the Uniswap V3 pool
|
|
|
|
|
(uint160 sqrtPriceX96, 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
|
|
|
|
|
|
|
|
// ## check price moved up
|
|
|
|
|
{
|
|
|
|
|
// Check if current tick is within the specified range
|
|
|
|
|
int24 anchorTickLower = positions[Stage.ANCHOR].tickLower;
|
|
|
|
|
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
|
2024-07-09 18:00:39 +02:00
|
|
|
|
|
|
|
|
int24 centerTick = token0isWeth ? anchorTickLower + ANCHOR_SPACING : anchorTickUpper - ANCHOR_SPACING;
|
2024-04-28 07:00:53 +02:00
|
|
|
uint256 minAmplitude = uint256(uint24((anchorTickUpper - anchorTickLower) * 3 / 20));
|
2024-04-03 21:43:12 +02:00
|
|
|
|
|
|
|
|
// Determine the correct comparison direction based on token0isWeth
|
2024-04-27 07:04:33 +02:00
|
|
|
bool isUp = token0isWeth ? currentTick < centerTick : currentTick > centerTick;
|
2024-04-28 07:00:53 +02:00
|
|
|
bool isEnough = SignedMath.abs(currentTick - centerTick) > minAmplitude;
|
2024-04-03 21:43:12 +02:00
|
|
|
|
|
|
|
|
// Check Conditions
|
|
|
|
|
require(isEnough, "amplitude not reached, come back later!");
|
2024-07-09 18:00:39 +02:00
|
|
|
require(isUp, "call slide(), not shift()");
|
2024-04-03 21:43:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ## scrape positions
|
2024-07-06 18:36:13 +02:00
|
|
|
_scrape();
|
2024-06-13 10:50:09 +02:00
|
|
|
harb.setPreviousTotalSupply(harb.totalSupply());
|
2024-07-06 18:36:13 +02:00
|
|
|
_set(sqrtPriceX96, currentTick);
|
2024-03-28 19:55:01 +01:00
|
|
|
}
|
|
|
|
|
|
2024-07-09 18:00:39 +02:00
|
|
|
|
2024-03-28 19:55:01 +01:00
|
|
|
function slide() external {
|
2024-04-03 21:43:12 +02:00
|
|
|
// Fetch the current tick from the Uniswap V3 pool
|
|
|
|
|
(uint160 sqrtPriceX96, 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
|
|
|
|
|
|
|
|
// ## check price moved down
|
|
|
|
|
if (positions[Stage.ANCHOR].liquidity > 0) {
|
|
|
|
|
// Check if current tick is within the specified range
|
|
|
|
|
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-04-29 06:27:28 +02:00
|
|
|
uint256 minAmplitude = uint256(uint24((anchorTickUpper - anchorTickLower) * 3 / 20));
|
2024-04-03 21:43:12 +02:00
|
|
|
|
|
|
|
|
// Determine the correct comparison direction based on token0isWeth
|
2024-04-29 06:27:28 +02:00
|
|
|
bool isDown = token0isWeth ? currentTick > centerTick : currentTick < centerTick;
|
|
|
|
|
bool isEnough = SignedMath.abs(currentTick - centerTick) > minAmplitude;
|
2024-04-03 21:43:12 +02:00
|
|
|
|
|
|
|
|
// Check Conditions
|
|
|
|
|
require(isEnough, "amplitude not reached, diamond hands!");
|
2024-07-09 18:00:39 +02:00
|
|
|
require(isDown, "call shift(), not slide()");
|
2024-04-03 21:43:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-09 16:06:41 +02:00
|
|
|
_scrape();
|
2024-07-06 18:36:13 +02:00
|
|
|
_set(sqrtPriceX96, currentTick);
|
2024-03-18 12:42:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|