mint & burn streamlining

This commit is contained in:
tim
2025-12-01 16:26:58 -04:00
parent 4e56f54f27
commit 6795886eab
6 changed files with 35 additions and 47 deletions

View File

@@ -72,7 +72,7 @@ contract PartyInfo is PartyPoolHelpers, IPartyInfo {
uint256 qd = pool.denominators()[quoteTokenIndex]; uint256 qd = pool.denominators()[quoteTokenIndex];
uint256 supply = pool.totalSupply(); uint256 supply = pool.totalSupply();
return value.mul(ABDKMath64x64.divu(qd * 10**18, supply)); return value.mul(ABDKMath64x64.divu(qd * LP_SCALE, supply));
} }

View File

@@ -38,10 +38,6 @@ abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGua
// LMSR internal state // LMSR internal state
LMSRStabilized.State internal _lmsr; LMSRStabilized.State internal _lmsr;
/// @notice Scale factor used when converting LMSR Q64.64 totals to LP token units (uint).
/// @dev LP _tokens are minted in units equal to ABDK.mulu(lastTotalQ64x64, LP_SCALE).
uint256 internal constant LP_SCALE = 1e18; // Scale used to convert LMSR lastTotal (Q64.64) into LP token units (uint)
/// @notice Token addresses comprising the pool. Effectively immutable after construction. /// @notice Token addresses comprising the pool. Effectively immutable after construction.
/// @dev _tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays. /// @dev _tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
IERC20[] internal _tokens; // effectively immutable since there is no interface to change the _tokens IERC20[] internal _tokens; // effectively immutable since there is no interface to change the _tokens

View File

@@ -6,6 +6,10 @@ import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
abstract contract PartyPoolHelpers { abstract contract PartyPoolHelpers {
using ABDKMath64x64 for int128; using ABDKMath64x64 for int128;
/// @notice Scale factor used when converting LMSR Q64.64 totals to LP token units (uint).
/// @dev LP _tokens are minted in units equal to ABDK.mulu(lastTotalQ64x64, LP_SCALE).
uint256 internal constant LP_SCALE = 1e18; // Scale used to convert LMSR lastTotal (Q64.64) into LP token units (uint)
/// @notice Ceiling fee helper: computes ceil(x * feePpm / 1_000_000) /// @notice Ceiling fee helper: computes ceil(x * feePpm / 1_000_000)
/// @dev Internal helper; public-facing functions use this to ensure fees round up in favor of pool. /// @dev Internal helper; public-facing functions use this to ensure fees round up in favor of pool.
function _ceilFee(uint256 x, uint256 feePpm) internal pure returns (uint256) { function _ceilFee(uint256 x, uint256 feePpm) internal pure returns (uint256) {

View File

@@ -91,19 +91,15 @@ contract PartyPoolMintImpl is PartyPoolBase {
uint256[] memory depositAmounts = mintAmounts(lpTokenAmount, _totalSupply, _cachedUintBalances); uint256[] memory depositAmounts = mintAmounts(lpTokenAmount, _totalSupply, _cachedUintBalances);
// Transfer in all token amounts // Transfer in all token amounts
for (uint i = 0; i < n; ) {
if (depositAmounts[i] > 0) {
_receiveTokenFrom(payer, _tokens[i], depositAmounts[i]);
}
unchecked { i++; }
}
// Update cached balances and internal q for all assets using depositAmounts
int128[] memory newQInternal = new int128[](n); int128[] memory newQInternal = new int128[](n);
for (uint i = 0; i < n; ) { for (uint i = 0; i < n; ) {
uint256 newBal = _cachedUintBalances[i] + depositAmounts[i]; uint256 amount = depositAmounts[i];
_cachedUintBalances[i] = newBal; if (amount > 0) {
newQInternal[i] = _uintToInternalFloor(newBal, _bases[i]); _receiveTokenFrom(payer, _tokens[i], amount);
uint256 newBal = _cachedUintBalances[i] + amount;
_cachedUintBalances[i] = newBal;
newQInternal[i] = _uintToInternalFloor(newBal, _bases[i]);
}
unchecked { i++; } unchecked { i++; }
} }
@@ -128,11 +124,6 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Ensure the calculated LP amount is not too different from requested // Ensure the calculated LP amount is not too different from requested
require(actualLpToMint > 0, "mint: zero LP minted"); require(actualLpToMint > 0, "mint: zero LP minted");
// Allow actual amount to be at most 0.00001% less than requested
// This accounts for rounding in deposit calculations
uint256 minAcceptable = lpTokenAmount * 99_999 / 100_000;
require(actualLpToMint >= minAcceptable, "mint: insufficient LP minted");
_mint(receiver, actualLpToMint); _mint(receiver, actualLpToMint);
emit IPartyPool.Mint(payer, receiver, depositAmounts, actualLpToMint); emit IPartyPool.Mint(payer, receiver, depositAmounts, actualLpToMint);
@@ -162,33 +153,23 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Compute proportional withdrawal amounts for the requested LP amount (rounded down) // Compute proportional withdrawal amounts for the requested LP amount (rounded down)
withdrawAmounts = burnAmounts(lpAmount, _totalSupply, _cachedUintBalances); withdrawAmounts = burnAmounts(lpAmount, _totalSupply, _cachedUintBalances);
// Transfer underlying _tokens out to receiver according to computed proportions
for (uint i = 0; i < n; ) {
if (withdrawAmounts[i] > 0) {
_sendTokenTo(_tokens[i], receiver, withdrawAmounts[i], unwrap);
}
unchecked { i++; }
}
// Update cached balances and internal q for all assets using computed withdrawals // Update cached balances and internal q for all assets using computed withdrawals
bool allZero = true;
int128[] memory newQInternal = new int128[](n); int128[] memory newQInternal = new int128[](n);
for (uint i = 0; i < n; ) { for (uint i = 0; i < n; ) {
uint256 newBal = _cachedUintBalances[i] - withdrawAmounts[i]; uint256 amount = withdrawAmounts[i];
_cachedUintBalances[i] = newBal; if (amount > 0) {
newQInternal[i] = _uintToInternalFloor(newBal, _bases[i]); _sendTokenTo(_tokens[i], receiver, amount, unwrap);
uint256 newBal = _cachedUintBalances[i] - amount;
_cachedUintBalances[i] = newBal;
newQInternal[i] = _uintToInternalFloor(newBal, _bases[i]);
if (newQInternal[i] != int128(0))
allZero = false;
}
unchecked { i++; } unchecked { i++; }
} }
// Apply proportional update or deinitialize if drained // Apply proportional update or deinitialize if drained
bool allZero = true;
for (uint i = 0; i < n; ) {
if (newQInternal[i] != int128(0)) {
allZero = false;
break;
}
unchecked { i++; }
}
if (allZero) { if (allZero) {
_lmsr.deinit(); _lmsr.deinit();
} else { } else {
@@ -226,6 +207,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Compute mint ratio in Q64.64: ratio = lpTokenAmount / totalSupply // Compute mint ratio in Q64.64: ratio = lpTokenAmount / totalSupply
int128 ratio = ABDKMath64x64.divu(lpTokenAmount, totalSupply); int128 ratio = ABDKMath64x64.divu(lpTokenAmount, totalSupply);
require(ratio > 0, 'mint too small');
// depositAmount_i = ceil(ratio * currentBalance_i) // depositAmount_i = ceil(ratio * currentBalance_i)
for (uint i = 0; i < numAssets; i++) { for (uint i = 0; i < numAssets; i++) {
@@ -248,11 +230,17 @@ contract PartyPoolMintImpl is PartyPoolBase {
} }
int128 ratio = ABDKMath64x64.divu(lpTokenAmount, totalSupply); int128 ratio = ABDKMath64x64.divu(lpTokenAmount, totalSupply);
require(ratio > 0, 'burn too small: tiny input');
bool nonZero = false;
for (uint i = 0; i < numAssets; i++) { for (uint i = 0; i < numAssets; i++) {
uint256 currentBalance = cachedUintBalances[i]; uint256 currentBalance = cachedUintBalances[i];
withdrawAmounts[i] = ratio.mulu(currentBalance); uint256 amount = ratio.mulu(currentBalance);
withdrawAmounts[i] = amount;
if (amount > 0)
nonZero = true;
} }
require(nonZero, 'burn too small: no output');
return withdrawAmounts; return withdrawAmounts;
} }

View File

@@ -461,7 +461,7 @@ contract GasTest is Test {
for (uint256 k = 0; k < iterations; k++) { for (uint256 k = 0; k < iterations; k++) {
// Request a tiny LP mint (1 wei) - pool will compute deposits and transfer from alice // Request a tiny LP mint (1 wei) - pool will compute deposits and transfer from alice
uint256 lpRequest = 1; uint256 lpRequest = testPool.totalSupply() / 10000;
// Snapshot alice LP before to compute actual minted // Snapshot alice LP before to compute actual minted
uint256 lpBefore = testPool.balanceOf(alice); uint256 lpBefore = testPool.balanceOf(alice);

View File

@@ -458,7 +458,7 @@ contract PartyPoolTest is Test {
// Use a range of LP requests (tiny to large fraction) // Use a range of LP requests (tiny to large fraction)
uint256 totalLp = pool.totalSupply(); uint256 totalLp = pool.totalSupply();
uint256[] memory requests = new uint256[](4); uint256[] memory requests = new uint256[](4);
requests[0] = 1; requests[0] = totalLp / 1000;
requests[1] = totalLp / 100; // 1% requests[1] = totalLp / 100; // 1%
requests[2] = totalLp / 10; // 10% requests[2] = totalLp / 10; // 10%
requests[3] = totalLp / 2; // 50% requests[3] = totalLp / 2; // 50%
@@ -504,7 +504,7 @@ contract PartyPoolTest is Test {
function testMintDepositAmountsMatchesMint_10TokenPool() public { function testMintDepositAmountsMatchesMint_10TokenPool() public {
uint256 totalLp = pool10.totalSupply(); uint256 totalLp = pool10.totalSupply();
uint256[] memory requests = new uint256[](4); uint256[] memory requests = new uint256[](4);
requests[0] = 1; requests[0] = totalLp / 1000;
requests[1] = totalLp / 100; requests[1] = totalLp / 100;
requests[2] = totalLp / 10; requests[2] = totalLp / 10;
requests[3] = totalLp / 2; requests[3] = totalLp / 2;
@@ -568,7 +568,7 @@ contract PartyPoolTest is Test {
// Use address(this) as payer (holds initial LP from setUp) // Use address(this) as payer (holds initial LP from setUp)
uint256 totalLp = pool.totalSupply(); uint256 totalLp = pool.totalSupply();
uint256[] memory burns = new uint256[](4); uint256[] memory burns = new uint256[](4);
burns[0] = 1; burns[0] = totalLp / 1000;
burns[1] = totalLp / 100; burns[1] = totalLp / 100;
burns[2] = totalLp / 10; burns[2] = totalLp / 10;
burns[3] = totalLp / 2; burns[3] = totalLp / 2;
@@ -620,7 +620,7 @@ contract PartyPoolTest is Test {
function testBurnReceiveAmountsMatchesBurn_10TokenPool() public { function testBurnReceiveAmountsMatchesBurn_10TokenPool() public {
uint256 totalLp = pool10.totalSupply(); uint256 totalLp = pool10.totalSupply();
uint256[] memory burns = new uint256[](4); uint256[] memory burns = new uint256[](4);
burns[0] = 1; burns[0] = totalLp / 1000;
burns[1] = totalLp / 100; burns[1] = totalLp / 100;
burns[2] = totalLp / 10; burns[2] = totalLp / 10;
burns[3] = totalLp / 2; burns[3] = totalLp / 2;