price() and poolPrice() fixes

This commit is contained in:
tim
2025-11-06 15:04:01 -04:00
parent 2b941e0969
commit de108cc1e4
4 changed files with 67 additions and 31 deletions

View File

@@ -94,6 +94,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
/// @inheritdoc IPartyPool
function denominators() external view returns (uint256[] memory) { return _bases; }
/// @inheritdoc IPartyPool
function LMSR() external view returns (LMSRStabilized.State memory) { return _lmsr; }

View File

@@ -139,13 +139,13 @@ contract PartyPoolMintImpl is PartyPoolBase {
return actualLpToMint;
}
/// @notice Burn LP _tokens and withdraw the proportional basket to receiver. Functional even if the pool has been
/// killed.
/// @dev Payer must own or approve the LP _tokens being burned. The function updates LMSR state
/// @notice Burn LP tokens and withdraw the proportional basket to receiver. Functional even if the pool
/// has been killed.
/// @dev Payer must own or approve the LP tokens being burned. The function updates LMSR state
/// proportionally to reflect the reduced pool size after the withdrawal.
/// @param payer address that provides the LP _tokens to burn
/// @param receiver address that receives the withdrawn _tokens
/// @param lpAmount amount of LP _tokens to burn (proportional withdrawal)
/// @param payer address that provides the LP tokens to burn
/// @param receiver address that receives the withdrawn tokens
/// @param lpAmount amount of LP tokens to burn (proportional withdrawal)
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
/// @param unwrap if true and the native token is being withdrawn, it is unwraped and sent as native currency
function burn(address payer, address receiver, uint256 lpAmount, uint256 deadline, bool unwrap) external nonReentrant

View File

@@ -1,14 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30;
import "forge-std/console2.sol";
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IPartyPool} from "./IPartyPool.sol";
import {IPartyPoolViewer} from "./IPartyPoolViewer.sol";
import {LMSRStabilized} from "./LMSRStabilized.sol";
import {PartyPoolHelpers} from "./PartyPoolHelpers.sol";
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
import {IPartyPoolViewer} from "./IPartyPoolViewer.sol";
contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
using ABDKMath64x64 for int128;
@@ -36,7 +37,11 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
uint256 nAssets = lmsr.qInternal.length;
require(nAssets > 0, "price: uninit");
require(baseTokenIndex < nAssets && quoteTokenIndex < nAssets, "price: idx");
return LMSRStabilized.price(pool.kappa(), lmsr.qInternal, baseTokenIndex, quoteTokenIndex);
int128 internalPrice = LMSRStabilized.price(pool.kappa(), lmsr.qInternal, baseTokenIndex, quoteTokenIndex);
// Convert to external units
uint256 bd = pool.denominators()[baseTokenIndex];
uint256 qd = pool.denominators()[quoteTokenIndex];
return internalPrice.mul(ABDKMath64x64.divu(bd, qd));
}
/// @notice Price of one LP token denominated in `quote` as Q64.64.
@@ -52,22 +57,7 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
require(quoteTokenIndex < nAssets, "poolPrice: idx");
// price per unit of qTotal (Q64.64) from LMSR
int128 pricePerQ = LMSRStabilized.poolPrice( pool.kappa(), lmsr.qInternal, quoteTokenIndex);
// total internal q (qTotal) as Q64.64
int128 qTotal = LMSRStabilized._computeSizeMetric(lmsr.qInternal);
require(qTotal > int128(0), "poolPrice: qTotal zero");
// totalSupply as Q64.64
uint256 supply = pool.totalSupply();
require(supply > 0, "poolPrice: zero supply");
int128 supplyQ64 = ABDKMath64x64.fromUInt(supply);
// factor = totalSupply / qTotal (Q64.64)
int128 factor = supplyQ64.div(qTotal);
// price per LP token = pricePerQ * factor (Q64.64)
return pricePerQ.mul(factor);
return LMSRStabilized.poolPrice( pool.kappa(), lmsr.qInternal, quoteTokenIndex);
}
@@ -171,11 +161,9 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
return IERC20(token).balanceOf(address(pool));
}
/**
* @dev The fee to be charged for a given loan.
* @param amount The amount of _tokens lent.
* @return fee The amount of `token` to be charged for the loan, on top of the returned principal.
*/
/// @dev The fee to be charged for a given loan.
/// @param amount The amount of _tokens lent.
/// @return fee The amount of `token` to be charged for the loan, on top of the returned principal.
function flashFee(
IPartyPool pool,
address /*token*/,

View File

@@ -176,7 +176,7 @@ contract PartyPoolTest is Test {
token2.transfer(address(pool), INIT_BAL);
// Perform initial mint (initial deposit); receiver is this contract
pool.initialMint(address(this), 0);
pool.initialMint(address(this), INIT_BAL * tokens.length * 10**18);
// Set up pool10 with 10 _tokens
IERC20[] memory tokens10 = new IERC20[](10);
@@ -1124,5 +1124,52 @@ contract PartyPoolTest is Test {
vm.stopPrank();
}
/// @notice Verify that the initial relative price between token0 and token1 is 1.0000000
function testInitialPriceIsOne() public view {
// Query the viewer for the relative price between token index 0 and 1
int128 price = viewer.price(pool, 0, 1);
// Expected price is 1.0 in ABDK 64.64 fixed point
int128 expected = ABDKMath64x64.fromInt(1);
// Cast int128 to uint128 then to uint256 for assertEq (values are non-negative)
assertEq(uint256(uint128(price)), uint256(uint128(expected)), "Initial relative price must be 1.0000000");
}
/// @notice Verify that the initial LP price in terms of token0 is 1.0000000
function testInitialPoolPriceIsOne() public {
// Query the viewer for the pool price for token0
int128 price = viewer.poolPrice(pool, 0);
// Expected price is 1.0 in ABDK 64.64 fixed point
int128 expected = ABDKMath64x64.fromInt(1);
// Cast int128 to uint128 then to uint256 for assertEq (values are non-negative)
assertEq(uint256(uint128(price)), uint256(uint128(expected)), "Initial pool price must be 1.0000000");
// Mint a small amount of LP into the pool from alice and verify price remains 1.0
vm.startPrank(alice);
// Approve tokens for pool to pull
token0.approve(address(pool), type(uint256).max);
token1.approve(address(pool), type(uint256).max);
token2.approve(address(pool), type(uint256).max);
// Choose a small LP request (1% of supply or at least 1)
uint256 lpRequest = pool.totalSupply() / 100;
if (lpRequest == 0) lpRequest = 1;
// Compute required deposits and perform mint if not trivial
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest);
bool allZero = true;
for (uint i = 0; i < deposits.length; i++) { if (deposits[i] != 0) { allZero = false; break; } }
if (!allZero) {
pool.mint(alice, alice, lpRequest, 0);
}
vm.stopPrank();
// Re-query the pool price and ensure it remains 1.0 (within exact fixed-point equality)
int128 priceAfter = viewer.poolPrice(pool, 0);
assertEq(uint256(uint128(priceAfter)), uint256(uint128(expected)), "Pool price should remain 1.0000000 after mint");
}
}
/* solhint-enable */