correct accumulator pricing
This commit is contained in:
parent
fd6899fb01
commit
c7582350a0
2 changed files with 53 additions and 32 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue