basic shift implementation
This commit is contained in:
parent
c41e7bc8e1
commit
ea1b007283
2 changed files with 118 additions and 103 deletions
|
|
@ -1,4 +1,5 @@
|
|||
@openzeppelin/=lib/openzeppelin-contracts/contracts/
|
||||
@uniswap-v3-core/=lib/uni-v3-lib/node_modules/@uniswap/v3-core/contracts/
|
||||
@uniswap-v3-periphery=lib/uni-v3-lib/node_modules/@uniswap/v3-periphery/contracts/
|
||||
@uniswap-v3-periphery/=lib/uni-v3-lib/node_modules/@uniswap/v3-periphery/contracts/
|
||||
@aperture/uni-v3-lib/=lib/uni-v3-lib/src/
|
||||
@abdk/=lib/abdk-libraries-solidity/
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ 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";
|
||||
import {ABDKMath64x64} from "abdk/ABDKMath64x64.sol";
|
||||
import {ABDKMath64x64} from "@abdk/ABDKMath64x64.sol";
|
||||
import "./interfaces/IWETH9.sol";
|
||||
import {Harb} from "./Harb.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @title LiquidityManager - A contract that supports the harb ecosystem. It
|
||||
* protects the communities liquidity while allowing a manager role to
|
||||
|
|
@ -49,7 +50,7 @@ contract BaseLineLP {
|
|||
uint256 feeGrowthInside1LastX128;
|
||||
}
|
||||
|
||||
mapping(uint256 => TokenPosition) positions;
|
||||
mapping(Stage => TokenPosition) positions;
|
||||
|
||||
modifier checkDeadline(uint256 deadline) {
|
||||
require(block.timestamp <= deadline, "Transaction too old");
|
||||
|
|
@ -82,13 +83,13 @@ contract BaseLineLP {
|
|||
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
|
||||
}
|
||||
|
||||
function createPosition(uint256 positionIndex, int24 tickLower, int24 tickUpper, uint256 amount) internal {
|
||||
function createPosition(Stage positionIndex, int24 tickLower, int24 tickUpper, uint256 amount) internal {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, amount
|
||||
);
|
||||
(uint256 amount0, uint256 amount1) = pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
|
||||
pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
|
||||
// TODO: check slippage
|
||||
// read position and start tracking in storage
|
||||
bytes32 positionKey = PositionKey.compute(address(this), tickLower, tickUpper);
|
||||
|
|
@ -116,197 +117,210 @@ contract BaseLineLP {
|
|||
if (token0isWeth) {
|
||||
tickLower = startTick;
|
||||
tickUpper = startTick + 200;
|
||||
createPosition(tickLower, tickUpper, amount / 10);
|
||||
createPosition(Stage.FLOOR, tickLower, tickUpper, amount / 10);
|
||||
} else {
|
||||
tickLower = startTick - 200;
|
||||
tickUpper = startTick;
|
||||
createPosition(tickLower, tickUpper, amount / 10);
|
||||
createPosition(Stage.FLOOR, tickLower, tickUpper, amount / 10);
|
||||
}
|
||||
|
||||
// create anchor
|
||||
if (token0isWeth) {
|
||||
tickLower += 201;
|
||||
tickUpper += 601;
|
||||
createPosition(tickLower, tickUpper, amount / 20);
|
||||
createPosition(Stage.ANCHOR, tickLower, tickUpper, amount / 20);
|
||||
} else {
|
||||
tickLower -= 601;
|
||||
tickUpper -= 201;
|
||||
createPosition(tickLower, tickUpper, amount / 10);
|
||||
createPosition(Stage.ANCHOR, tickLower, tickUpper, amount / 10);
|
||||
}
|
||||
|
||||
// create discovery
|
||||
if (token0isWeth) {
|
||||
tickLower += 601;
|
||||
tickUpper += 11001;
|
||||
createPosition(tickLower, tickUpper, harb.balanceOf(address(this)));
|
||||
createPosition(Stage.DISCOVERY, tickLower, tickUpper, harb.balanceOf(address(this)));
|
||||
} else {
|
||||
tickLower -= 11001;
|
||||
tickUpper -= 601;
|
||||
createPosition(tickLower, tickUpper, harb.balanceOf(address(this)));
|
||||
createPosition(Stage.DISCOVERY, tickLower, tickUpper, harb.balanceOf(address(this)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function outstanding() public view returns (uint256 _outstanding) {
|
||||
harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this));
|
||||
_outstanding = harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this));
|
||||
}
|
||||
|
||||
function ethInAnchor() public view returns (uint256 _ethInAnchor) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(positions[Stage.ANCHOR].tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(positions[Stage.ANCHOR].tickUpper);
|
||||
function ethIn(Stage s) public view returns (uint256 _ethInPosition) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(positions[s].tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(positions[s].tickUpper);
|
||||
if (token0isWeth) {
|
||||
_ethInAnchor = LiquidityAmounts.getAmount0ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, positions[Stage.ANCHOR].liquidity
|
||||
_ethInPosition = LiquidityAmounts.getAmount0ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity
|
||||
);
|
||||
} else {
|
||||
_ethInAnchor = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, positions[Stage.ANCHOR].liquidity
|
||||
_ethInPosition = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Calculates cycle tick from cycle price.
|
||||
function tickAtPrice(uint256 price) internal view returns (int24 tick_) {
|
||||
tick_ = TickMath.getTickAtSqrtRatio(
|
||||
uint160(int160(ABDKMath64x64.sqrt(int128(int256(price << 64))) << 32))
|
||||
function tickAtPrice(uint256 tokenAmount, uint256 ethAmount) internal view returns (int24 tick_) {
|
||||
require(ethAmount > 0, "ETH amount cannot be zero");
|
||||
// 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.
|
||||
uint160 sqrtPriceX96 = uint160(
|
||||
int160(ABDKMath64x64.sqrt(priceRatio) << 32)
|
||||
);
|
||||
// Proceed as before.
|
||||
tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
|
||||
tick_ = tick_ / TICK_SPACING * TICK_SPACING;
|
||||
tick_ = token0isWeth ? tick_ : -tick_;
|
||||
}
|
||||
|
||||
function _mint(int24 tickLower, int24 tickUpper, uint128 liquidity) internal {
|
||||
// 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
|
||||
positions[Stage.ANCHOR] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: tickLower,
|
||||
tickUpper: tickUpper,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
}
|
||||
|
||||
// call this function when price has moved up 15%
|
||||
function shift() external {
|
||||
// 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 = anchor.tickLower;
|
||||
int24 anchorTickUpper = anchor.tickUpper;
|
||||
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);
|
||||
int24 amplitudeTick = anchorTickLower + (anchorTickUpper - anchorTickLower) * 3 / 20;
|
||||
if (token0isWeth) {
|
||||
require(currentTick > centerTick, "call slide(), not shift()");
|
||||
// ### check price moved more than 15% of range
|
||||
require(currentTick > amplitudeTick, "amplitude not reached, come back later");
|
||||
} else {
|
||||
// ### check price moved up enough and add hysteresis
|
||||
require(currentTick < centerTick, "call slide(), not shift()");
|
||||
// ### check price moved more than 15% of range
|
||||
require(currentTick < amplitudeTick, "amplitude not reached, come back later");
|
||||
}
|
||||
|
||||
// Determine the correct comparison direction based on token0isWeth
|
||||
bool isUp = token0isWeth ? currentTick > centerTick : currentTick < centerTick;
|
||||
bool isEnough = token0isWeth ? currentTick > amplitudeTick : currentTick < amplitudeTick;
|
||||
|
||||
// Check Conditions
|
||||
require(isUp, "call slide(), not shift()");
|
||||
require(isEnough, "amplitude not reached, come back later");
|
||||
}
|
||||
|
||||
// ## scrape positions
|
||||
uint256 ethAmountAnchor;
|
||||
for (uint256 i=Stage.FLOOR; i <= Stage.DISCOVERY; i++) {
|
||||
position = positions[i];
|
||||
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);
|
||||
if (i == ANCHOR) {
|
||||
ethAmountAnchor = token0isWeth ? amount0 : amount1;
|
||||
if (i == uint256(Stage.ANCHOR)) {
|
||||
ethInAnchor = token0isWeth ? amount0 : amount1;
|
||||
}
|
||||
}
|
||||
// TODO: handle fees
|
||||
|
||||
// ## set new positions
|
||||
// move 10% of new ETH into Floor
|
||||
ethAmountAnchor -= (ethAmountAnchor - ethInAnchor()) * 10 / LIQUIDITY_RATIO_DIVISOR;
|
||||
// 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;
|
||||
|
||||
// ### set Floor position
|
||||
uint256 anchorLiquidity;
|
||||
// ### set Anchor position
|
||||
uint128 anchorLiquidity;
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(currentTick - 300);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick + 300);
|
||||
int24 tickLower = currentTick - 300;
|
||||
int24 tickUpper = currentTick + 300;
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
if (token0isWeth) {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethAmountAnchor
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInAnchor
|
||||
);
|
||||
} else {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethAmountAnchor
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInAnchor
|
||||
);
|
||||
}
|
||||
(uint256 amount0, uint256 amount1) = pool.mint(address(this), currentTick - 300, currentTick + 300, liquidity, abi.encode(poolKey));
|
||||
bytes32 positionKey = PositionKey.compute(address(this), currentTick - 300, currentTick + 300);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
positions[Stage.ANCHOR] = TokenPosition({
|
||||
liquidity: anchorLiquidity,
|
||||
tickLower: currentTick - 300,
|
||||
tickUpper: currentTick + 300,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
_mint(tickLower, tickUpper, anchorLiquidity);
|
||||
}
|
||||
|
||||
// ### set Floor position
|
||||
{
|
||||
uint256 ethAmountFloor = address(this).balance;
|
||||
int24 floorTick = tickAtPrice(outstanding() / ethAmountFloor);
|
||||
floorTick -= (currentTick - floorTick);
|
||||
int24 startTick = token0isWeth ? currentTick - 301 : currentTick + 301;
|
||||
|
||||
// all remaining eth will be put into this position
|
||||
uint256 ethInFloor = address(this).balance;
|
||||
// calculate price at which all HARB can be bought back
|
||||
int24 floorTick = tickAtPrice(outstanding(), ethInFloor);
|
||||
// put a position symetrically around the price, startTick being edge on one side
|
||||
floorTick = token0isWeth ? floorTick - (startTick - floorTick) : startTick + (floorTick - startTick);
|
||||
// calculate liquidity
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorTick);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick - 300);
|
||||
uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(startTick);
|
||||
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
||||
sqrtPriceX96,
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
token0isWeth ? ethAmountFloor : 0,
|
||||
token0isWeth ? 0: ethAmountFloor
|
||||
token0isWeth ? ethInFloor : 0,
|
||||
token0isWeth ? 0: ethInFloor
|
||||
);
|
||||
pool.mint(
|
||||
address(this),
|
||||
floorTick,
|
||||
currentTick - 300,
|
||||
liquidity,
|
||||
abi.encode(poolKey)
|
||||
);
|
||||
bytes32 positionKey = PositionKey.compute(address(this), floorTick, currentTick - 300);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
positions[Stage.FLOOR] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: floorTick,
|
||||
tickUpper: currentTick - 300,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
// mint
|
||||
_mint(floorTick, startTick, liquidity);
|
||||
}
|
||||
|
||||
// ##set discovery position
|
||||
// ## set Discovery position
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(currentTick + 300);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick + 11300);
|
||||
int24 tickLower = token0isWeth ? currentTick + 301 : currentTick - 11301;
|
||||
int24 tickUpper = token0isWeth ? currentTick + 11301 : currentTick - 301;
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
// discovery with 1.5 times as much liquidity per tick as anchor
|
||||
uint256 liquidity = anchorLiquidity * 55 / 2;
|
||||
// A * 3 11000 A * 55
|
||||
// D = ----- * ----- = ------
|
||||
// 2 600 2
|
||||
uint128 liquidity = anchorLiquidity * 55 / 2;
|
||||
uint256 harbInDiscovery;
|
||||
if (token0isWeth) {
|
||||
harbInDiscovery = TickMath.getAmount1ForLiquidity(
|
||||
harbInDiscovery = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
liquidity
|
||||
);
|
||||
} else {
|
||||
harbInDiscovery = TickMath.getAmount0ForLiquidity(
|
||||
harbInDiscovery = LiquidityAmounts.getAmount0ForLiquidity(
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
liquidity
|
||||
);
|
||||
}
|
||||
pool.mint(
|
||||
address(this),
|
||||
currentTick + 300,
|
||||
currentTick + 11300,
|
||||
liquidity,
|
||||
abi.encode(poolKey)
|
||||
);
|
||||
bytes32 positionKey = PositionKey.compute(address(this), currentTick + 300, currentTick + 11300);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
positions[Stage.DISCOVERY] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: floorTick,
|
||||
tickUpper: currentTick - 300,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
_mint(tickLower, tickUpper, liquidity);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue