mint & burn streamlining
This commit is contained in:
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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];
|
||||||
|
if (amount > 0) {
|
||||||
|
_receiveTokenFrom(payer, _tokens[i], amount);
|
||||||
|
uint256 newBal = _cachedUintBalances[i] + amount;
|
||||||
_cachedUintBalances[i] = newBal;
|
_cachedUintBalances[i] = newBal;
|
||||||
newQInternal[i] = _uintToInternalFloor(newBal, _bases[i]);
|
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];
|
||||||
|
if (amount > 0) {
|
||||||
|
_sendTokenTo(_tokens[i], receiver, amount, unwrap);
|
||||||
|
uint256 newBal = _cachedUintBalances[i] - amount;
|
||||||
_cachedUintBalances[i] = newBal;
|
_cachedUintBalances[i] = newBal;
|
||||||
newQInternal[i] = _uintToInternalFloor(newBal, _bases[i]);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user