correct accumulator pricing

This commit is contained in:
JulesCrown 2024-08-12 19:07:07 +02:00
parent fd6899fb01
commit c7582350a0
2 changed files with 53 additions and 32 deletions

View file

@ -10,6 +10,7 @@ import "@aperture/uni-v3-lib/PoolAddress.sol";
import "@aperture/uni-v3-lib/CallbackValidation.sol";
import "@openzeppelin/token/ERC20/IERC20.sol";
import "@openzeppelin/utils/math/SignedMath.sol";
import {Math} from "@openzeppelin/utils/math/Math.sol";
import {ABDKMath64x64} from "@abdk/ABDKMath64x64.sol";
import "./interfaces/IWETH9.sol";
import {Harberg} from "./Harberg.sol";
@ -26,8 +27,9 @@ import {Harberg} from "./Harberg.sol";
* @dev Utilizes Uniswap V3's concentrated liquidity feature, enabling highly efficient use of capital.
*/
contract LiquidityManager {
using Math for uint256;
// State variables to track total ETH spent
uint256 public cumulativeVolumeWeightedPrice;
uint256 public cumulativeVolumeWeightedPriceX96;
uint256 public cumulativeVolume;
// the minimum granularity of liquidity positions in the Uniswap V3 pool. this is a 1% pool.
int24 internal constant TICK_SPACING = 200;
@ -154,6 +156,8 @@ contract LiquidityManager {
receive() external payable {
}
/// @notice Calculates the Uniswap V3 tick corresponding to a given price ratio between Harberg and ETH.
/// @param t0isWeth Boolean flag indicating if token0 is WETH.
/// @param tokenAmount Amount of the Harberg token.
@ -161,32 +165,35 @@ contract LiquidityManager {
/// @return tick_ The calculated tick for the given price ratio.
function tickAtPrice(bool t0isWeth, uint256 tokenAmount, uint256 ethAmount) internal pure returns (int24 tick_) {
require(ethAmount > 0, "ETH amount cannot be zero");
uint160 sqrtPriceX96;
if (tokenAmount == 0) {
sqrtPriceX96 = MIN_SQRT_RATIO;
tick_ = t0isWeth ? TickMath.MIN_TICK : TickMath.MAX_TICK;
} 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 priceRatioX64 = 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)
);
tick_ = tickAtPriceRatio(t0isWeth, priceRatioX64);
}
}
function tickAtPriceRatio(bool t0isWeth, int128 priceRatioX64) internal pure returns (int24 tick_) {
// Convert the price ratio into a sqrt price in the format expected by Uniswap's TickMath.
uint160 sqrtPriceX96 = uint160(
int160(ABDKMath64x64.sqrt(priceRatioX64) << 32)
);
tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
tick_ = t0isWeth ? tick_ : -tick_;
}
/// @notice Calculates the price ratio from a given Uniswap V3 tick.
/// @notice Calculates the price ratio from a given Uniswap V3 tick as HARB/ETH.
/// @param tick The tick for which to calculate the price ratio.
/// @return priceRatio The price ratio corresponding to the given tick.
function tickToPrice(int24 tick) public pure returns (uint256 priceRatio) {
uint160 sqrtRatio = TickMath.getSqrtRatioAtTick(tick);
uint256 adjustedSqrtRatio = uint256(sqrtRatio) / (1 << 48);
priceRatio = adjustedSqrtRatio * adjustedSqrtRatio;
/// @return priceRatioX96 The price ratio corresponding to the given tick.
function priceAtTick(int24 tick) private pure returns (uint256 priceRatioX96) {
//tick = (tick < 0) ? -tick : tick;
uint256 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);
priceRatioX96 = sqrtRatioX96.mulDiv(sqrtRatioX96, (1 << 96));
}
/// @notice Internal function to mint liquidity positions in the Uniswap V3 pool.
@ -221,11 +228,11 @@ contract LiquidityManager {
int24 vwapTick;
{
uint256 outstandingSupply = harb.outstandingSupply();
uint256 vwap = 0;
uint256 vwapX96 = 0;
uint256 requiredEthForBuyback = 0;
if (cumulativeVolume > 0) {
vwap = cumulativeVolumeWeightedPrice / cumulativeVolume;
requiredEthForBuyback = outstandingSupply * 10**18 / vwap;
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
@ -234,16 +241,17 @@ contract LiquidityManager {
// not enough ETH, find a lower price
requiredEthForBuyback = floorEthBalance;
vwapTick = tickAtPrice(token0isWeth, outstandingSupply * capitalInfefficiency / 100 , requiredEthForBuyback);
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwap, capitalInfefficiency, anchorLiquidityShare, vwapTick);
} else if (vwap == 0) {
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, capitalInfefficiency, anchorLiquidityShare, vwapTick);
} else if (vwapX96 == 0) {
requiredEthForBuyback = floorEthBalance;
vwapTick = currentTick;
} else {
// recalculate vwap with capital inefficiency
vwap = cumulativeVolumeWeightedPrice * capitalInfefficiency / 100 / cumulativeVolume; // in harb/eth
vwapTick = tickAtPrice(token0isWeth, token0isWeth ? vwap : 10**18, token0isWeth ? 10**18 : vwap);
vwapX96 = cumulativeVolumeWeightedPriceX96 * capitalInfefficiency / 100 / cumulativeVolume; // in harb/eth
vwapTick = tickAtPriceRatio(token0isWeth, int128(int256 (vwapX96 >> 32)));
vwapTick = token0isWeth ? vwapTick : -vwapTick;
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwap, capitalInfefficiency, anchorLiquidityShare, vwapTick);
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, capitalInfefficiency, anchorLiquidityShare, vwapTick);
}
// never make floor smaller than anchor
if (requiredEthForBuyback < ethBalance * 3 / 4) {
@ -332,23 +340,23 @@ contract LiquidityManager {
}
}
function _recordVolumeAndPrice(uint256 currentPrice, uint256 fee) internal {
function _recordVolumeAndPrice(uint256 currentPriceX96, uint256 fee) internal {
// assuming FEE is 1%
uint256 volume = fee * 100;
uint256 volumeWeightedPrice = currentPrice * volume;
uint256 volumeWeightedPriceX96 = currentPriceX96 * volume;
// Check for potential overflow. 10**70 is close to 2^256
if (cumulativeVolumeWeightedPrice > 10**70) {
if (cumulativeVolumeWeightedPriceX96 > 10**70) {
uint256 zipFactor = 10**35;
uint256 desiredPrecision = 10**5;
while (zipFactor * desiredPrecision > cumulativeVolume) {
zipFactor /= desiredPrecision;
}
// Handle overflow: zip historic trade data
cumulativeVolumeWeightedPrice = cumulativeVolumeWeightedPrice / zipFactor;
cumulativeVolumeWeightedPriceX96 = cumulativeVolumeWeightedPriceX96 / zipFactor;
// cumulativeVolume should be well higer than zipFactor
cumulativeVolume = cumulativeVolume / zipFactor;
}
cumulativeVolumeWeightedPrice += volumeWeightedPrice;
cumulativeVolumeWeightedPriceX96 += volumeWeightedPriceX96;
cumulativeVolume += volume;
}
@ -374,7 +382,7 @@ contract LiquidityManager {
if (i == uint256(Stage.ANCHOR)) {
// the historic archor position is only an approximation for the price
int24 tick = token0isWeth ? -1 * (position.tickLower + ANCHOR_SPACING): position.tickUpper - ANCHOR_SPACING;
currentPrice = tickToPrice(tick);
currentPrice = priceAtTick(tick);
}
}
}

View file

@ -390,23 +390,23 @@ contract LiquidityManagerTest is Test {
);
uint256 cumulativeVolumeWeightedPrice = lm.cumulativeVolumeWeightedPrice();
uint256 cumulativeVolumeWeightedPriceX96 = lm.cumulativeVolumeWeightedPriceX96();
uint256 beforeCumulativeVolume = lm.cumulativeVolume();
assertGt(cumulativeVolumeWeightedPrice, type(uint256).max / 2, "Initial cumulativeVolumeWeightedPrice is not near max uint256");
assertGt(cumulativeVolumeWeightedPriceX96, type(uint256).max / 2, "Initial cumulativeVolumeWeightedPrice is not near max uint256");
buy(50 ether);
shift();
cumulativeVolumeWeightedPrice = lm.cumulativeVolumeWeightedPrice();
cumulativeVolumeWeightedPriceX96 = lm.cumulativeVolumeWeightedPriceX96();
uint256 cumulativeVolume = lm.cumulativeVolume();
// Assert that the values after wrap-around are valid and smaller than max uint256
assertGt(beforeCumulativeVolume, cumulativeVolume, "cumulativeVolume after wrap-around is smaller than before");
// Assert that the price is reasonable
uint256 calculatedPrice = cumulativeVolumeWeightedPrice / cumulativeVolume;
uint256 calculatedPrice = cumulativeVolumeWeightedPriceX96 / cumulativeVolume;
assertTrue(calculatedPrice > 0 && calculatedPrice < 10**40, "Calculated price after wrap-around is not within a reasonable range");
}
@ -520,6 +520,18 @@ contract LiquidityManagerTest is Test {
// shift();
// sell(harberg.balanceOf(account) / 4);
// slide(true);
// sell(harberg.balanceOf(account) / 4);
// slide(true);
// sell(harberg.balanceOf(account) / 4);
// slide(true);
// sell(harberg.balanceOf(account));
// slide(true);
@ -529,6 +541,7 @@ contract LiquidityManagerTest is Test {
// console.log(traderBalanceBefore);
// console.log(traderBalanceAfter);
// assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
// revert();
// }
function testScenarioFuzz(uint8 numActions, uint8 frequency, uint8[] calldata amounts) public {