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";
|
|
|
|
|
import "@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-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
|
|
|
/**
|
|
|
|
|
* @title LiquidityManager - A contract that supports the harb ecosystem. It
|
|
|
|
|
* protects the communities liquidity while allowing a manager role to
|
|
|
|
|
* take strategic liqudity positions.
|
|
|
|
|
*/
|
|
|
|
|
contract BaseLineLP {
|
2024-03-28 19:55:01 +01:00
|
|
|
int24 constant TICK_SPACING = 200;
|
2024-04-23 06:58:34 +02:00
|
|
|
int24 constant ANCHOR_SPACING = 5 * TICK_SPACING;
|
2024-04-27 07:04:33 +02:00
|
|
|
int24 constant DISCOVERY_SPACING = 11000;
|
2024-03-18 12:42:30 +01:00
|
|
|
// default fee of 1%
|
|
|
|
|
uint24 constant FEE = uint24(10_000);
|
2024-03-28 19:55:01 +01:00
|
|
|
// uint256 constant FLOOR = 0;
|
|
|
|
|
// uint256 constant ANCHOR = 1;
|
|
|
|
|
// uint256 constant DISCOVERY = 2;
|
|
|
|
|
|
|
|
|
|
enum Stage { FLOOR, ANCHOR, DISCOVERY }
|
|
|
|
|
|
|
|
|
|
uint256 constant LIQUIDITY_RATIO_DIVISOR = 100;
|
2024-03-18 12:42:30 +01:00
|
|
|
|
|
|
|
|
// the address of the Uniswap V3 factory
|
|
|
|
|
address immutable factory;
|
|
|
|
|
IWETH9 immutable weth;
|
|
|
|
|
Harb immutable harb;
|
|
|
|
|
IUniswapV3Pool immutable pool;
|
|
|
|
|
PoolKey private poolKey;
|
|
|
|
|
bool immutable token0isWeth;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct TokenPosition {
|
|
|
|
|
// the liquidity of the position
|
|
|
|
|
uint128 liquidity;
|
|
|
|
|
int24 tickLower;
|
|
|
|
|
int24 tickUpper;
|
|
|
|
|
// the fee growth of the aggregate position as of the last action on the individual position
|
|
|
|
|
uint256 feeGrowthInside0LastX128;
|
|
|
|
|
uint256 feeGrowthInside1LastX128;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-06 07:32:55 +02:00
|
|
|
// for minting limits
|
|
|
|
|
uint256 private lastDay;
|
|
|
|
|
uint256 private mintedToday;
|
|
|
|
|
|
2024-03-28 21:50:22 +01:00
|
|
|
mapping(Stage => TokenPosition) positions;
|
2024-03-18 12:42:30 +01:00
|
|
|
|
|
|
|
|
modifier checkDeadline(uint256 deadline) {
|
|
|
|
|
require(block.timestamp <= deadline, "Transaction too old");
|
|
|
|
|
_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @notice Emitted when liquidity is increased for a position
|
|
|
|
|
/// @param liquidity The amount by which liquidity for the NFT position was increased
|
|
|
|
|
/// @param amount0 The amount of token0 that was paid for the increase in liquidity
|
|
|
|
|
/// @param amount1 The amount of token1 that was paid for the increase in liquidity
|
|
|
|
|
event IncreaseLiquidity(int24 indexed tickLower, int24 indexed tickUpper, uint128 liquidity, uint256 amount0, uint256 amount1);
|
|
|
|
|
/// @notice Emitted when liquidity is decreased for a position
|
|
|
|
|
/// @param liquidity The amount by which liquidity for the NFT position was decreased
|
|
|
|
|
/// @param ethReceived The amount of WETH that was accounted for the decrease in liquidity
|
|
|
|
|
event PositionLiquidated(int24 indexed tickLower, int24 indexed tickUpper, uint128 liquidity, uint256 ethReceived);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-11 07:28:54 +02:00
|
|
|
event UniCallback(uint256 indexed amount0Owed, uint256 indexed amount1Owed);
|
|
|
|
|
|
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
|
|
|
|
|
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-04-11 07:28:54 +02:00
|
|
|
receive() external payable {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-28 21:50:22 +01:00
|
|
|
function createPosition(Stage positionIndex, int24 tickLower, int24 tickUpper, uint256 amount) internal {
|
2024-03-18 12:42:30 +01:00
|
|
|
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
|
|
|
|
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
|
|
|
|
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
2024-03-28 19:55:01 +01:00
|
|
|
sqrtRatioAX96, sqrtRatioBX96, amount
|
2024-03-18 12:42:30 +01:00
|
|
|
);
|
2024-03-28 21:50:22 +01:00
|
|
|
pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
|
2024-03-28 19:55:01 +01:00
|
|
|
// TODO: check slippage
|
2024-03-18 12:42:30 +01:00
|
|
|
// read position and start tracking in storage
|
|
|
|
|
bytes32 positionKey = PositionKey.compute(address(this), tickLower, tickUpper);
|
|
|
|
|
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
2024-03-28 19:55:01 +01:00
|
|
|
positions[positionIndex] = TokenPosition({
|
2024-03-18 12:42:30 +01:00
|
|
|
liquidity: liquidity,
|
|
|
|
|
tickLower: tickLower,
|
|
|
|
|
tickUpper: tickUpper,
|
|
|
|
|
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
|
|
|
|
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-03-28 19:55:01 +01:00
|
|
|
function outstanding() public view returns (uint256 _outstanding) {
|
2024-03-28 21:50:22 +01:00
|
|
|
_outstanding = harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this));
|
2024-03-28 19:55:01 +01:00
|
|
|
}
|
2024-03-18 12:42:30 +01:00
|
|
|
|
2024-04-06 07:32:55 +02:00
|
|
|
function spendingLimit() public view returns (uint256, uint256) {
|
|
|
|
|
return (lastDay, mintedToday);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 07:00:53 +02:00
|
|
|
|
2024-03-28 21:50:22 +01:00
|
|
|
function ethIn(Stage s) public view returns (uint256 _ethInPosition) {
|
|
|
|
|
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(positions[s].tickLower);
|
|
|
|
|
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(positions[s].tickUpper);
|
2024-03-28 19:55:01 +01:00
|
|
|
if (token0isWeth) {
|
2024-03-28 21:50:22 +01:00
|
|
|
_ethInPosition = LiquidityAmounts.getAmount0ForLiquidity(
|
2024-04-28 07:00:53 +02:00
|
|
|
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity / 2
|
2024-03-28 19:55:01 +01:00
|
|
|
);
|
2024-03-18 12:42:30 +01:00
|
|
|
} else {
|
2024-03-28 21:50:22 +01:00
|
|
|
_ethInPosition = LiquidityAmounts.getAmount1ForLiquidity(
|
2024-04-28 07:00:53 +02:00
|
|
|
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity / 2
|
2024-03-28 19:55:01 +01:00
|
|
|
);
|
2024-03-18 12:42:30 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-11 07:28:54 +02:00
|
|
|
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
|
|
|
|
|
|
2024-04-28 07:00:53 +02:00
|
|
|
function tickAtPrice(uint256 tokenAmount, uint256 ethAmount) internal 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
|
|
|
// Proceed as before.
|
|
|
|
|
tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
|
2024-03-28 19:55:01 +01:00
|
|
|
tick_ = tick_ / TICK_SPACING * TICK_SPACING;
|
|
|
|
|
tick_ = token0isWeth ? tick_ : -tick_;
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// get fee data
|
|
|
|
|
bytes32 positionKey = PositionKey.compute(
|
|
|
|
|
address(this),
|
|
|
|
|
tickLower,
|
|
|
|
|
tickUpper
|
|
|
|
|
);
|
|
|
|
|
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
tickUpper: tickUpper,
|
|
|
|
|
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
|
|
|
|
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-06 07:32:55 +02:00
|
|
|
/// @dev Returns if amount is within daily limit and resets spentToday after one day.
|
|
|
|
|
/// @param amount Amount to withdraw.
|
|
|
|
|
/// @return Returns if amount is under daily limit.
|
|
|
|
|
function availableMint(uint256 amount) internal returns (uint256) {
|
|
|
|
|
if (block.timestamp > lastDay + 24 hours) {
|
|
|
|
|
lastDay = block.timestamp;
|
|
|
|
|
mintedToday = 0;
|
|
|
|
|
}
|
|
|
|
|
uint256 mintLimit = harb.totalSupply() * 3 / 20;
|
|
|
|
|
if (mintedToday + amount > mintLimit) {
|
|
|
|
|
return mintLimit - mintedToday;
|
|
|
|
|
}
|
|
|
|
|
return amount;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-23 06:58:34 +02:00
|
|
|
event DEBUG(uint256 indexed eth, uint256 indexed outstanding, int24 indexed floorTick, int24 startTick, bool ethIs0);
|
2024-04-11 07:28:54 +02:00
|
|
|
|
2024-04-03 21:43:12 +02:00
|
|
|
function _set(uint160 sqrtPriceX96, int24 currentTick, uint256 ethInNewAnchor) internal {
|
2024-03-28 21:50:22 +01:00
|
|
|
// ### set Anchor position
|
|
|
|
|
uint128 anchorLiquidity;
|
2024-03-18 12:42:30 +01:00
|
|
|
{
|
2024-04-23 06:58:34 +02:00
|
|
|
int24 tickLower = currentTick - ANCHOR_SPACING;
|
|
|
|
|
int24 tickUpper = currentTick + 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
|
|
|
if (token0isWeth) {
|
2024-04-23 06:58:34 +02:00
|
|
|
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
|
2024-04-03 21:43:12 +02:00
|
|
|
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
|
2024-03-28 19:55:01 +01:00
|
|
|
);
|
|
|
|
|
} else {
|
2024-04-23 06:58:34 +02:00
|
|
|
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
|
2024-04-03 21:43:12 +02:00
|
|
|
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
|
2024-03-28 19:55:01 +01:00
|
|
|
);
|
|
|
|
|
}
|
2024-04-23 06:58:34 +02:00
|
|
|
// TODO: calculate liquidity correctly
|
|
|
|
|
// or make sure that we don't have to pay more than we have
|
2024-04-27 07:04:33 +02:00
|
|
|
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity * 2);
|
2024-03-18 12:42:30 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-28 19:55:01 +01:00
|
|
|
// ### set Floor position
|
2024-03-18 12:42:30 +01:00
|
|
|
{
|
2024-04-23 06:58:34 +02:00
|
|
|
int24 startTick = token0isWeth ? currentTick + ANCHOR_SPACING : currentTick - ANCHOR_SPACING;
|
2024-03-28 21:50:22 +01:00
|
|
|
|
|
|
|
|
// all remaining eth will be put into this position
|
2024-04-28 07:00:53 +02:00
|
|
|
uint256 ethInFloor = address(this).balance + weth.balanceOf(address(this));
|
2024-04-11 21:41:48 +02:00
|
|
|
int24 floorTick;
|
2024-03-28 21:50:22 +01:00
|
|
|
// calculate price at which all HARB can be bought back
|
2024-04-11 21:41:48 +02:00
|
|
|
uint256 _outstanding = outstanding();
|
|
|
|
|
if (_outstanding > 0) {
|
|
|
|
|
floorTick = tickAtPrice(_outstanding, ethInFloor);
|
|
|
|
|
// put a position symetrically around the price, startTick being edge on one side
|
|
|
|
|
floorTick = token0isWeth ? startTick + (floorTick - startTick) : floorTick - (startTick - floorTick);
|
2024-04-29 06:27:28 +02:00
|
|
|
|
|
|
|
|
bool isOvercollateralized = token0isWeth ? floorTick < startTick : floorTick > startTick;
|
|
|
|
|
if (isOvercollateralized) {
|
|
|
|
|
floorTick = startTick + ((token0isWeth ? int24(1) : int24(-1)) * 400);
|
|
|
|
|
}
|
2024-04-11 21:41:48 +02:00
|
|
|
} else {
|
2024-04-23 06:58:34 +02:00
|
|
|
floorTick = startTick + ((token0isWeth ? int24(1) : int24(-1)) * 400);
|
2024-04-11 21:41:48 +02:00
|
|
|
}
|
2024-03-28 21:50:22 +01:00
|
|
|
// calculate liquidity
|
2024-03-28 19:55:01 +01:00
|
|
|
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorTick);
|
2024-03-28 21:50:22 +01:00
|
|
|
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(startTick);
|
|
|
|
|
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
2024-03-28 19:55:01 +01:00
|
|
|
sqrtPriceX96,
|
|
|
|
|
sqrtRatioAX96,
|
|
|
|
|
sqrtRatioBX96,
|
2024-04-23 06:58:34 +02:00
|
|
|
token0isWeth ? ethInFloor : 0,
|
|
|
|
|
token0isWeth ? 0 : ethInFloor
|
2024-03-28 19:55:01 +01:00
|
|
|
);
|
2024-04-29 06:27:28 +02:00
|
|
|
|
2024-03-28 21:50:22 +01:00
|
|
|
// mint
|
2024-04-27 07:04:33 +02:00
|
|
|
_mint(Stage.FLOOR, startTick, floorTick, liquidity);
|
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)));
|
|
|
|
|
// // manage minting limits of harb here
|
|
|
|
|
// if (harbInDiscovery <= harb.balanceOf(address(this))) {
|
|
|
|
|
// _mint(tickLower, tickUpper, liquidity);
|
|
|
|
|
// harb.burn(harb.balanceOf(address(this)));
|
|
|
|
|
// } else {
|
|
|
|
|
// uint256 amount = availableMint(harbInDiscovery - harb.balanceOf(address(this)));
|
|
|
|
|
// harb.mint(amount);
|
|
|
|
|
// mintedToday += amount;
|
|
|
|
|
// amount = harb.balanceOf(address(this));
|
|
|
|
|
// if(amount < harbInDiscovery) {
|
|
|
|
|
// // calculate new ticks so that discovery liquidity is still
|
|
|
|
|
// // deeper than anchor, but less wide
|
|
|
|
|
// int24 tickWidth = int24(int256(11000 * amount / harbInDiscovery)) + ANCHOR_SPACING;
|
|
|
|
|
|
|
|
|
|
// tickWidth = tickWidth / TICK_SPACING * TICK_SPACING;
|
|
|
|
|
// tickWidth = (tickWidth <= ANCHOR_SPACING) ? tickWidth + TICK_SPACING : tickWidth;
|
|
|
|
|
// tickLower = token0isWeth ? currentTick - tickWidth : currentTick + ANCHOR_SPACING;
|
|
|
|
|
// tickUpper = token0isWeth ? currentTick - ANCHOR_SPACING : currentTick + tickWidth;
|
|
|
|
|
// sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
|
|
|
|
// sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
|
|
|
|
// liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
|
|
|
|
// sqrtPriceX96,
|
|
|
|
|
// sqrtRatioAX96,
|
|
|
|
|
// sqrtRatioBX96,
|
|
|
|
|
// token0isWeth ? 0 : amount,
|
|
|
|
|
// token0isWeth ? amount : 0
|
|
|
|
|
// );
|
|
|
|
|
// }
|
|
|
|
|
// _mint(tickLower, tickUpper, liquidity);
|
|
|
|
|
// }
|
2024-03-18 12:42:30 +01:00
|
|
|
}
|
2024-04-03 21:43:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// call this function when price has moved up 15%
|
|
|
|
|
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();
|
|
|
|
|
// TODO: check slippage with oracle
|
|
|
|
|
|
|
|
|
|
// ## 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;
|
|
|
|
|
// center tick can be calculated positive and negative numbers the same
|
|
|
|
|
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
|
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(isUp, "call slide(), not shift()");
|
|
|
|
|
require(isEnough, "amplitude not reached, come back later!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ## scrape positions
|
|
|
|
|
uint256 ethInAnchor;
|
|
|
|
|
for (uint256 i=uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
|
|
|
|
|
TokenPosition storage position = positions[Stage(i)];
|
|
|
|
|
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
|
2024-04-28 07:00:53 +02:00
|
|
|
// the actual amounts collected are returned
|
|
|
|
|
(amount0, amount1) = pool.collect(
|
|
|
|
|
address(this),
|
|
|
|
|
position.tickLower,
|
|
|
|
|
position.tickUpper,
|
|
|
|
|
uint128(amount0),
|
|
|
|
|
uint128(amount1)
|
|
|
|
|
);
|
2024-04-03 21:43:12 +02:00
|
|
|
if (i == uint256(Stage.ANCHOR)) {
|
|
|
|
|
ethInAnchor = token0isWeth ? amount0 : amount1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// TODO: handle fees
|
2024-04-28 07:00:53 +02:00
|
|
|
|
2024-04-03 21:43:12 +02:00
|
|
|
// ## set new positions
|
|
|
|
|
// reduce Anchor by 10% of new ETH. It will be moved into Floor
|
|
|
|
|
uint256 initialEthInAnchor = ethIn(Stage.ANCHOR);
|
|
|
|
|
ethInAnchor -= (ethInAnchor - initialEthInAnchor) * 10 / LIQUIDITY_RATIO_DIVISOR;
|
|
|
|
|
|
2024-04-28 07:00:53 +02:00
|
|
|
|
2024-04-03 21:43:12 +02:00
|
|
|
// cap anchor size at 10 % of total ETH
|
2024-04-28 07:00:53 +02:00
|
|
|
uint256 ethBalance = address(this).balance + weth.balanceOf(address(this));
|
2024-04-03 21:43:12 +02:00
|
|
|
ethInAnchor = (ethInAnchor > ethBalance / 10) ? ethBalance / 10 : ethInAnchor;
|
|
|
|
|
|
2024-04-28 07:00:53 +02:00
|
|
|
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
2024-04-03 21:43:12 +02:00
|
|
|
_set(sqrtPriceX96, currentTick, ethInAnchor);
|
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();
|
|
|
|
|
// TODO: check slippage with oracle
|
|
|
|
|
|
|
|
|
|
// ## 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-04-29 06:27:28 +02:00
|
|
|
emit DEBUG(0, 0, anchorTickLower, anchorTickUpper, token0isWeth);
|
2024-04-03 21:43:12 +02:00
|
|
|
// center tick can be calculated positive and negative numbers the same
|
|
|
|
|
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
|
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;
|
|
|
|
|
emit DEBUG(minAmplitude, 0, currentTick, centerTick, token0isWeth);
|
|
|
|
|
bool isEnough = SignedMath.abs(currentTick - centerTick) > minAmplitude;
|
2024-04-03 21:43:12 +02:00
|
|
|
|
|
|
|
|
// Check Conditions
|
|
|
|
|
require(isDown, "call shift(), not slide()");
|
|
|
|
|
require(isEnough, "amplitude not reached, diamond hands!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ## scrape positions
|
|
|
|
|
for (uint256 i=uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
|
|
|
|
|
TokenPosition storage position = positions[Stage(i)];
|
|
|
|
|
if (position.liquidity > 0) {
|
2024-04-28 07:00:53 +02:00
|
|
|
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
|
|
|
|
|
(amount0, amount1) = pool.collect(
|
|
|
|
|
address(this),
|
|
|
|
|
position.tickLower,
|
|
|
|
|
position.tickUpper,
|
|
|
|
|
uint128(amount0),
|
|
|
|
|
uint128(amount1)
|
|
|
|
|
);
|
2024-04-03 21:43:12 +02:00
|
|
|
// TODO: handle fees
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-28 07:00:53 +02:00
|
|
|
uint256 ethBalance = address(this).balance + weth.balanceOf(address(this));
|
2024-04-03 21:43:12 +02:00
|
|
|
if (ethBalance == 0) {
|
|
|
|
|
// TODO: set only discovery
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
uint256 ethInAnchor = ethIn(Stage.ANCHOR);
|
|
|
|
|
uint256 ethInFloor = ethIn(Stage.FLOOR);
|
|
|
|
|
|
|
|
|
|
// use previous ration of Floor to Anchor
|
2024-04-11 07:28:54 +02:00
|
|
|
uint256 ethInNewAnchor = ethBalance / 10;
|
|
|
|
|
if (ethInFloor > 0) {
|
|
|
|
|
ethInNewAnchor = ethBalance * ethInAnchor / (ethInAnchor + ethInFloor);
|
|
|
|
|
}
|
2024-04-03 21:43:12 +02:00
|
|
|
|
|
|
|
|
// but cap anchor size at 10 % of total ETH
|
|
|
|
|
ethInNewAnchor = (ethInNewAnchor > ethBalance / 10) ? ethBalance / 10 : ethInNewAnchor;
|
|
|
|
|
|
2024-04-11 07:28:54 +02:00
|
|
|
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
2024-04-03 21:43:12 +02:00
|
|
|
_set(sqrtPriceX96, currentTick, ethInNewAnchor);
|
2024-03-18 12:42:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|