take harb minted for staking into account when setting floor

This commit is contained in:
JulesCrown 2024-08-15 15:17:44 +02:00
parent 3eeef258e3
commit 16e65f0f15
2 changed files with 96 additions and 88 deletions

View file

@ -36,15 +36,12 @@ contract LiquidityManager {
// defines the width of the anchor position from the current price to discovery position.
int24 internal constant ANCHOR_SPACING = 5 * TICK_SPACING;
// DISCOVERY_SPACING determines the range above the current price where new tokens are minted and sold.
// This spacing is much wider, allowing the contract to place liquidity far from the current market price,
// aiming to capture potential price increases and support token issuance within strategic market bounds.
// 11000 ticks represent 3x the current price
int24 internal constant DISCOVERY_SPACING = 11000;
// how much more liquidity per tick discovery is holding over anchor
uint128 internal constant DISCOVERY_DEPTH = 450; // 500 // 500%
int24 internal constant MAX_TICK_DEVIATION = 50; // how much is that?
uint128 internal constant DISCOVERY_DEPTH = 200; // 500 // 500%
// only working with UNI V3 1% fee tier pools
uint24 internal constant FEE = uint24(10_000);
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
// ANCHOR_LIQ_SHARE is the mininum share of total ETH in control
// that will be left to put into anchor positon.
uint256 internal constant MIN_ANCHOR_LIQ_SHARE = 5; // 5 = 5%
@ -55,6 +52,7 @@ contract LiquidityManager {
uint256 internal constant MAX_CAPITAL_INEFFICIENCY = 200;
// 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?
// the address of the Uniswap V3 factory
address private immutable factory;
@ -63,6 +61,7 @@ contract LiquidityManager {
IUniswapV3Pool private immutable pool;
bool private immutable token0isWeth;
PoolKey private poolKey;
uint256 private harbPulled; // temporary variable to store amount of harb pulled by Uni
// the 3 positions this contract is managing
enum Stage { FLOOR, ANCHOR, DISCOVERY }
@ -117,7 +116,8 @@ contract LiquidityManager {
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
CallbackValidation.verifyCallback(factory, poolKey);
// take care of harb
harb.mint(token0isWeth ? amount1Owed : amount0Owed);
harbPulled = token0isWeth ? amount1Owed : amount0Owed;
harb.mint(harbPulled);
// pack ETH
uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed;
if (weth.balanceOf(address(this)) < ethOwed) {
@ -227,70 +227,25 @@ contract LiquidityManager {
/// @dev Recalculates and realigns all liquidity positions according to the latest market data and strategic requirements.
function _set(int24 currentTick) internal {
// set Floor position
// estimate the lower tick of the anchor
int24 vwapTick;
{
uint256 outstandingSupply = harb.outstandingSupply();
uint256 vwapX96 = 0;
uint256 requiredEthForBuyback = 0;
if (cumulativeVolume > 0) {
vwapX96 = cumulativeVolumeWeightedPriceX96 / cumulativeVolume;
requiredEthForBuyback = outstandingSupply.mulDiv(vwapX96, (1 << 96));
}
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
// leave at least ANCHOR_LIQ_SHARE% of supply for anchor
uint256 floorEthBalance = ethBalance * (100 - anchorLiquidityShare) / 100;
if (floorEthBalance < requiredEthForBuyback) {
// not enough ETH, find a lower price
requiredEthForBuyback = floorEthBalance;
vwapTick = tickAtPrice(token0isWeth, outstandingSupply * capitalInfefficiency / 100 , requiredEthForBuyback);
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, capitalInfefficiency, anchorLiquidityShare, vwapTick);
} else if (vwapX96 == 0) {
requiredEthForBuyback = floorEthBalance;
vwapTick = currentTick;
} else {
// recalculate vwap with capital inefficiency
vwapX96 = cumulativeVolumeWeightedPriceX96 * capitalInfefficiency / 100 / cumulativeVolume; // in harb/eth
// ETH/HARB tick
vwapTick = tickAtPriceRatio(int128(int256(vwapX96 >> 32)));
// convert to pool tick
vwapTick = token0isWeth ? -vwapTick : vwapTick;
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, capitalInfefficiency, anchorLiquidityShare, vwapTick);
}
// never make floor smaller than anchor
if (requiredEthForBuyback < ethBalance * 3 / 4) {
// use 3/4 instead of 1/2 to also account for liquidity of harb in anchor
requiredEthForBuyback = ethBalance * 3 / 4;
}
// 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;
}
uint256 outstandingSupply = harb.outstandingSupply();
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
uint256 floorEthBalance = ethBalance * (100 - anchorLiquidityShare) / 100;
if (outstandingSupply > 0) {
vwapTick = tickAtPrice(token0isWeth, outstandingSupply * capitalInfefficiency / 100 , floorEthBalance);
} else {
vwapTick = token0isWeth ? currentTick + ANCHOR_SPACING : currentTick - ANCHOR_SPACING;
}
// normalize tick position for pool
vwapTick = vwapTick / TICK_SPACING * TICK_SPACING;
// calculate liquidity
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(vwapTick);
int24 floorTick = token0isWeth ? vwapTick + TICK_SPACING: vwapTick - TICK_SPACING;
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorTick);
uint128 liquidity;
if (token0isWeth) {
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, requiredEthForBuyback
);
} else {
liquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioBX96, requiredEthForBuyback
);
}
_mint(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
// 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
uint128 anchorLiquidity;
uint24 anchorWidth;
{
int24 tickLower = token0isWeth ? currentTick - ANCHOR_SPACING : vwapTick;
int24 tickUpper = token0isWeth ? vwapTick : currentTick + ANCHOR_SPACING;
@ -300,21 +255,22 @@ contract LiquidityManager {
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
// adding a 2% balance margin, because liquidity calculations are inherently unprecise
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this))) * 98 / 100;
uint256 anchorEthBalance = ethBalance - floorEthBalance;
uint128 anchorLiquidity;
if (token0isWeth) {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioX96, sqrtRatioBX96, ethBalance
sqrtRatioX96, sqrtRatioBX96, anchorEthBalance
);
} else {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioX96, ethBalance
sqrtRatioAX96, sqrtRatioX96, anchorEthBalance
);
}
anchorWidth = uint24(tickUpper - tickLower);
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
}
harb.burn(harb.balanceOf(address(this)));
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
// set Discovery position
@ -323,25 +279,77 @@ contract LiquidityManager {
int24 tickUpper = token0isWeth ? currentTick - ANCHOR_SPACING : currentTick + DISCOVERY_SPACING + ANCHOR_SPACING;
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
// discovery with x times as much liquidity per tick as anchor
uint128 liquidity = anchorLiquidity * uint128(uint24(DISCOVERY_SPACING)) * DISCOVERY_DEPTH / 100 / anchorWidth;
uint256 harbInDiscovery;
uint256 discoveryAmount = harbPulled * uint24(DISCOVERY_SPACING) * uint24(DISCOVERY_DEPTH) / uint24(ANCHOR_SPACING) / 100;
uint128 liquidity;
if (token0isWeth) {
harbInDiscovery = LiquidityAmounts.getAmount0ForLiquidity(
sqrtRatioAX96,
sqrtRatioBX96,
liquidity
liquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioBX96, discoveryAmount
);
} else {
harbInDiscovery = LiquidityAmounts.getAmount1ForLiquidity(
sqrtRatioAX96,
sqrtRatioBX96,
liquidity
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, discoveryAmount
);
}
_mint(Stage.DISCOVERY, tickLower, tickUpper, liquidity);
harb.burn(harb.balanceOf(address(this)));
}
// set Floor position
{
outstandingSupply = harb.outstandingSupply();
uint256 vwapX96 = 0;
uint256 requiredEthForBuyback = 0;
if (cumulativeVolume > 0) {
vwapX96 = cumulativeVolumeWeightedPriceX96 * capitalInfefficiency / 100 / cumulativeVolume; // in harb/eth
requiredEthForBuyback = outstandingSupply.mulDiv(vwapX96, (1 << 96));
}
// make a new calculation of the vwapTick, having updated outstandingSupply
if (floorEthBalance < requiredEthForBuyback) {
// not enough ETH, find a lower price
requiredEthForBuyback = floorEthBalance;
vwapTick = tickAtPrice(token0isWeth, outstandingSupply * capitalInfefficiency / 100 , requiredEthForBuyback);
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, capitalInfefficiency, anchorLiquidityShare, vwapTick);
} else if (vwapX96 == 0) {
requiredEthForBuyback = floorEthBalance;
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, capitalInfefficiency, anchorLiquidityShare, vwapTick);
}
// 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);
int24 floorTick = token0isWeth ? vwapTick + TICK_SPACING: vwapTick - TICK_SPACING;
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorTick);
// adding a 2% balance margin, because liquidity calculations are inherently unprecise
floorEthBalance = floorEthBalance * 98 / 100;
uint128 liquidity;
if (token0isWeth) {
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, floorEthBalance
);
} else {
liquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioBX96, floorEthBalance
);
}
_mint(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
}
}
function _recordVolumeAndPrice(uint256 currentPriceX96, uint256 fee) internal {

View file

@ -398,7 +398,7 @@ contract LiquidityManagerTest is Test {
assertGt(cumulativeVolumeWeightedPriceX96, type(uint256).max / 2, "Initial cumulativeVolumeWeightedPrice is not near max uint256");
buy(50 ether);
buy(25 ether);
shift();
@ -498,28 +498,28 @@ contract LiquidityManagerTest is Test {
// function testScenarioB() public {
// setUpCustomToken0(false);
// vm.deal(account, 201 ether);
// vm.deal(account, 501 ether);
// vm.prank(account);
// weth.deposit{value: 201 ether}();
// weth.deposit{value: 501 ether}();
// uint256 traderBalanceBefore = weth.balanceOf(account);
// // Setup initial liquidity
// slide(false);
// buy(50 ether);
// buy(25 ether);
// shift();
// buy(50 ether);
// buy(45 ether);
// shift();
// buy(50 ether);
// buy(80 ether);
// shift();
// buy(50 ether);
// buy(120 ether);
// shift();