vwap
This commit is contained in:
parent
aa67c0d798
commit
92fd80d5ce
2 changed files with 219 additions and 160 deletions
|
|
@ -36,6 +36,7 @@ contract BaseLineLP {
|
|||
|
||||
enum Stage { FLOOR, ANCHOR, DISCOVERY }
|
||||
|
||||
uint256 constant CAPITAL_INEFFICIENCY = 120;
|
||||
uint256 constant LIQUIDITY_RATIO_DIVISOR = 100;
|
||||
|
||||
// the address of the Uniswap V3 factory
|
||||
|
|
@ -58,6 +59,11 @@ contract BaseLineLP {
|
|||
uint256 private lastDay;
|
||||
uint256 private mintedToday;
|
||||
|
||||
// State variables to track total ETH spent
|
||||
uint256 public cumulativeVolumeWeightedPrice;
|
||||
uint256 public cumulativeVolume;
|
||||
|
||||
|
||||
mapping(Stage => TokenPosition) public positions;
|
||||
|
||||
address private feeDestination;
|
||||
|
|
@ -118,7 +124,9 @@ contract BaseLineLP {
|
|||
}
|
||||
|
||||
function outstanding() public view returns (uint256 _outstanding) {
|
||||
_outstanding = harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this));
|
||||
//_outstanding = (harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this)));
|
||||
// This introduces capital inefficiency, but haven't found another way yet to protect capital from whale attacks
|
||||
_outstanding = (harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this))) * CAPITAL_INEFFICIENCY / 100;
|
||||
}
|
||||
|
||||
function spendingLimit() public view returns (uint256, uint256) {
|
||||
|
|
@ -213,82 +221,97 @@ contract BaseLineLP {
|
|||
});
|
||||
}
|
||||
|
||||
/// @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;
|
||||
|
||||
// Calculate current VWAP
|
||||
function calculateVWAP() public view returns (uint256) {
|
||||
if (cumulativeVolume == 0) return 0;
|
||||
return cumulativeVolumeWeightedPrice / cumulativeVolume;
|
||||
}
|
||||
|
||||
function _set(uint160 sqrtPriceX96, int24 currentTick, uint256 ethInNewAnchor) internal {
|
||||
// ### set Anchor position
|
||||
uint128 anchorLiquidity;
|
||||
{
|
||||
int24 tickLower = currentTick - ANCHOR_SPACING;
|
||||
int24 tickUpper = currentTick + ANCHOR_SPACING;
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
if (token0isWeth) {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
|
||||
);
|
||||
} else {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
|
||||
);
|
||||
}
|
||||
// TODO: calculate liquidity correctly
|
||||
// or make sure that we don't have to pay more than we have
|
||||
tickLower = tickLower / TICK_SPACING * TICK_SPACING;
|
||||
tickUpper = tickUpper / TICK_SPACING * TICK_SPACING;
|
||||
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity * 2);
|
||||
}
|
||||
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
||||
function _set(uint160 sqrtPriceX96, int24 currentTick) internal {
|
||||
|
||||
// ### set Floor position
|
||||
int24 vwapTick;
|
||||
{
|
||||
int24 startTick = token0isWeth ? currentTick + ANCHOR_SPACING : currentTick - ANCHOR_SPACING;
|
||||
|
||||
// all remaining eth will be put into this position
|
||||
uint256 ethInFloor = address(this).balance + weth.balanceOf(address(this));
|
||||
int24 floorTick;
|
||||
// calculate price at which all HARB can be bought back
|
||||
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);
|
||||
|
||||
bool isOvercollateralized = token0isWeth ? floorTick < startTick : floorTick > startTick;
|
||||
if (isOvercollateralized) {
|
||||
floorTick = startTick + ((token0isWeth ? int24(1) : int24(-1)) * 400);
|
||||
}
|
||||
} else {
|
||||
floorTick = startTick + ((token0isWeth ? int24(1) : int24(-1)) * 400);
|
||||
uint256 outstandingSupply = outstanding();
|
||||
uint256 vwap = 0;
|
||||
uint256 requiredEthForBuyback = 0;
|
||||
if (cumulativeVolume > 0) {
|
||||
vwap = cumulativeVolumeWeightedPrice / cumulativeVolume;
|
||||
requiredEthForBuyback = outstandingSupply / vwap * 10**18;
|
||||
}
|
||||
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
|
||||
// leave at least 5% of supply for anchor
|
||||
ethBalance = ethBalance * 90 / 100;
|
||||
if (ethBalance < requiredEthForBuyback) {
|
||||
// not enough ETH, find a lower price
|
||||
requiredEthForBuyback = ethBalance;
|
||||
// put the price 5% lower than needed
|
||||
vwapTick = tickAtPrice(outstandingSupply, requiredEthForBuyback);
|
||||
} else if (vwap == 0) {
|
||||
requiredEthForBuyback = ethBalance;
|
||||
vwapTick = currentTick;
|
||||
} else {
|
||||
// put the price 5% lower than needed
|
||||
vwapTick = tickAtPrice(cumulativeVolumeWeightedPrice / 10**18, cumulativeVolume);
|
||||
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;
|
||||
|
||||
int24 floorTick = token0isWeth ? vwapTick + TICK_SPACING: vwapTick - TICK_SPACING;
|
||||
|
||||
// calculate liquidity
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorTick);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(startTick);
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(vwapTick);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorTick);
|
||||
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
||||
sqrtPriceX96,
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
token0isWeth ? ethInFloor : 0,
|
||||
token0isWeth ? 0 : ethInFloor
|
||||
token0isWeth ? requiredEthForBuyback : 0,
|
||||
token0isWeth ? 0 : requiredEthForBuyback
|
||||
);
|
||||
|
||||
// mint
|
||||
_mint(Stage.FLOOR, token0isWeth ? startTick : floorTick, token0isWeth ? floorTick : startTick, liquidity);
|
||||
_mint(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
|
||||
}
|
||||
|
||||
// ### set Anchor position
|
||||
uint128 anchorLiquidity;
|
||||
{
|
||||
int24 tickLower = token0isWeth ? currentTick - ANCHOR_SPACING : vwapTick;
|
||||
int24 tickUpper = token0isWeth ? vwapTick : currentTick + ANCHOR_SPACING;
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
|
||||
if (token0isWeth) {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethBalance
|
||||
);
|
||||
} else {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethBalance
|
||||
);
|
||||
}
|
||||
tickLower = tickLower / TICK_SPACING * TICK_SPACING;
|
||||
tickUpper = tickUpper / TICK_SPACING * TICK_SPACING;
|
||||
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
|
||||
}
|
||||
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
||||
|
||||
|
||||
|
||||
// ## set Discovery position
|
||||
{
|
||||
int24 tickLower = token0isWeth ? currentTick - DISCOVERY_SPACING - ANCHOR_SPACING : currentTick + ANCHOR_SPACING;
|
||||
|
|
@ -320,9 +343,20 @@ contract BaseLineLP {
|
|||
}
|
||||
}
|
||||
|
||||
function _scrape() internal returns (uint256 ethInAnchor) {
|
||||
function tickToPrice(int24 tick) public pure returns (uint256) {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick);
|
||||
// Convert the sqrt price to price using fixed point arithmetic
|
||||
// sqrtPriceX96 is a Q64.96 format (96 fractional bits)
|
||||
// price = (sqrtPriceX96 ** 2) / 2**192
|
||||
// To avoid overflow, perform the division by 2**96 first before squaring
|
||||
uint256 price = uint256(sqrtPriceX96) / (1 << 48); // Reducing the scale before squaring
|
||||
return price * price;
|
||||
}
|
||||
|
||||
function _scrape() internal {
|
||||
uint256 fee0 = 0;
|
||||
uint256 fee1 = 0;
|
||||
uint256 currentPrice;
|
||||
for (uint256 i=uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
|
||||
TokenPosition storage position = positions[Stage(i)];
|
||||
if (position.liquidity > 0) {
|
||||
|
|
@ -339,20 +373,38 @@ contract BaseLineLP {
|
|||
fee0 += collected0 - amount0;
|
||||
fee1 += collected1 - amount1;
|
||||
if (i == uint256(Stage.ANCHOR)) {
|
||||
ethInAnchor = token0isWeth ? amount0 : amount1;
|
||||
int24 priceTick = position.tickLower + (position.tickUpper - position.tickLower);
|
||||
currentPrice = tickToPrice(priceTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer fees to the fee destination
|
||||
// and record transaction totals
|
||||
if (fee0 > 0) {
|
||||
IERC20(token0isWeth ? address(weth): address(harb)).transfer(feeDestination, fee0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (fee1 > 0) {
|
||||
IERC20(token0isWeth ? address(harb): address(weth)).transfer(feeDestination, fee1);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _isPriceStable(int24 currentTick) internal view returns (bool) {
|
||||
uint32 timeInterval = 300; // 5 minutes in seconds
|
||||
uint32[] memory secondsAgo = new uint32[](2);
|
||||
|
|
@ -394,21 +446,9 @@ contract BaseLineLP {
|
|||
}
|
||||
|
||||
// ## scrape positions
|
||||
uint256 ethInAnchor = _scrape();
|
||||
|
||||
// ## set new positions
|
||||
// reduce Anchor by 10% of new ETH. It will be moved into Floor
|
||||
(uint256 initialEthInAnchor,) = tokensIn(Stage.ANCHOR);
|
||||
ethInAnchor -= (ethInAnchor - initialEthInAnchor) * 10 / LIQUIDITY_RATIO_DIVISOR;
|
||||
|
||||
|
||||
// cap anchor size at 10 % of total ETH
|
||||
uint256 ethBalance = address(this).balance + weth.balanceOf(address(this));
|
||||
ethInAnchor = (ethInAnchor > ethBalance / 10) ? ethBalance / 10 : ethInAnchor;
|
||||
|
||||
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
||||
_scrape();
|
||||
harb.setPreviousTotalSupply(harb.totalSupply());
|
||||
_set(sqrtPriceX96, currentTick, ethInAnchor);
|
||||
_set(sqrtPriceX96, currentTick);
|
||||
}
|
||||
|
||||
function slide() external {
|
||||
|
|
@ -437,26 +477,7 @@ contract BaseLineLP {
|
|||
}
|
||||
|
||||
_scrape();
|
||||
|
||||
uint256 ethBalance = address(this).balance + weth.balanceOf(address(this));
|
||||
if (ethBalance == 0) {
|
||||
// TODO: set only discovery
|
||||
return;
|
||||
}
|
||||
(uint256 ethInAnchor,) = tokensIn(Stage.ANCHOR);
|
||||
(uint256 ethInFloor,) = tokensIn(Stage.FLOOR);
|
||||
|
||||
// use previous ration of Floor to Anchor
|
||||
uint256 ethInNewAnchor = ethBalance / 10;
|
||||
if (ethInFloor > 0) {
|
||||
ethInNewAnchor = ethBalance * ethInAnchor / (ethInAnchor + ethInFloor);
|
||||
}
|
||||
|
||||
// but cap anchor size at 10 % of total ETH
|
||||
ethInNewAnchor = (ethInNewAnchor > ethBalance / 10) ? ethBalance / 10 : ethInNewAnchor;
|
||||
|
||||
//currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
||||
_set(sqrtPriceX96, currentTick, ethInNewAnchor);
|
||||
_set(sqrtPriceX96, currentTick);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue