PartyPoolMintImpl

This commit is contained in:
tim
2025-09-26 11:48:01 -04:00
parent 9cac58013b
commit 28b9474363
5 changed files with 264 additions and 134 deletions

View File

@@ -48,6 +48,9 @@ contract PartyPool is PartyPoolBase, IPartyPool {
/// @notice Address of the SwapMint implementation contract for delegatecall
address public immutable swapMintImpl;
/// @notice Address of the Mint implementation contract for delegatecall
address public immutable mintImpl;
/// @inheritdoc IPartyPool
function getToken(uint256 i) external view returns (IERC20) { return tokens[i]; }
@@ -69,6 +72,7 @@ contract PartyPool is PartyPoolBase, IPartyPool {
/// @param _flashFeePpm fee in parts-per-million, taken for flash loans
/// @param _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated.
/// @param _swapMintImpl address of the SwapMint implementation contract
/// @param _mintImpl address of the Mint implementation contract
constructor(
string memory name_,
string memory symbol_,
@@ -78,7 +82,8 @@ contract PartyPool is PartyPoolBase, IPartyPool {
uint256 _swapFeePpm,
uint256 _flashFeePpm,
bool _stable,
PartyPoolSwapMintImpl _swapMintImpl
PartyPoolSwapMintImpl _swapMintImpl,
address _mintImpl
) PartyPoolBase(name_, symbol_) {
require(tokens_.length > 1, "Pool: need >1 asset");
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
@@ -90,8 +95,10 @@ contract PartyPool is PartyPoolBase, IPartyPool {
require(_flashFeePpm < 1_000_000, "Pool: flash fee >= ppm");
flashFeePpm = _flashFeePpm;
_stablePair = _stable && tokens_.length == 2;
require(address(_swapMintImpl) != address(0), "Pool: impl address zero");
require(address(_swapMintImpl) != address(0), "Pool: swapMintImpl address zero");
swapMintImpl = address(_swapMintImpl);
require(_mintImpl != address(0), "Pool: mintImpl address zero");
mintImpl = _mintImpl;
uint256 n = tokens_.length;
@@ -115,6 +122,10 @@ contract PartyPool is PartyPoolBase, IPartyPool {
/// @inheritdoc IPartyPool
function mintDepositAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
return _mintDepositAmounts(lpTokenAmount);
}
function _mintDepositAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory depositAmounts) {
uint256 n = tokens.length;
depositAmounts = new uint256[](n);
@@ -180,77 +191,23 @@ contract PartyPool is PartyPoolBase, IPartyPool {
}
/// @notice Proportional mint for existing pool.
/// @dev Payer must approve the required token amounts before calling.
/// Can only be called when pool is already initialized (totalSupply() > 0 and lmsr.nAssets > 0).
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
/// @dev This function forwards the call to the mint implementation via delegatecall
/// @param payer address that provides the input tokens
/// @param receiver address that receives the LP tokens
/// @param lpTokenAmount desired amount of LP tokens to mint
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external nonReentrant
returns (uint256 lpMinted) {
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
uint256 n = tokens.length;
bytes memory data = abi.encodeWithSignature(
"mint(address,address,uint256,uint256)",
payer,
receiver,
lpTokenAmount,
deadline
);
// Check if this is NOT initial deposit - revert if it is
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
require(!isInitialDeposit, "mint: use initialMint for pool initialization");
require(lpTokenAmount > 0, "mint: zero LP amount");
// Capture old pool size metric (scaled) by computing from current balances
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
// Calculate required deposit amounts for the desired LP tokens
uint256[] memory depositAmounts = mintDepositAmounts(lpTokenAmount);
// Transfer in all token amounts
for (uint i = 0; i < n; ) {
if (depositAmounts[i] > 0) {
tokens[i].safeTransferFrom(payer, address(this), depositAmounts[i]);
}
unchecked { i++; }
}
// Update cached balances for all assets
int128[] memory newQInternal = new int128[](n);
for (uint i = 0; i < n; ) {
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
cachedUintBalances[i] = bal;
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
unchecked { i++; }
}
// Update for proportional change
lmsr.updateForProportionalChange(newQInternal);
// Compute actual LP tokens to mint based on change in size metric (scaled)
// floor truncation rounds in favor of the pool
int128 newTotal = _computeSizeMetric(newQInternal);
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
uint256 actualLpToMint;
require(oldScaled > 0, "mint: oldScaled zero");
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
// Proportional issuance: totalSupply * delta / oldScaled
if (delta > 0) {
// floor truncation rounds in favor of the pool
actualLpToMint = (totalSupply() * delta) / oldScaled;
} else {
actualLpToMint = 0;
}
// Ensure the calculated LP amount is not too different from requested
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);
emit Mint(payer, receiver, depositAmounts, actualLpToMint);
return actualLpToMint;
bytes memory result = Address.functionDelegateCall(mintImpl, data);
return abi.decode(result, (uint256));
}
/// @inheritdoc IPartyPool
@@ -280,74 +237,21 @@ contract PartyPool is PartyPoolBase, IPartyPool {
}
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
/// @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.
/// @dev This function forwards the call to the burn implementation via delegatecall
/// @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.
function burn(address payer, address receiver, uint256 lpAmount, uint256 deadline) external nonReentrant {
require(deadline == 0 || block.timestamp <= deadline, "burn: deadline exceeded");
uint256 n = tokens.length;
require(lpAmount > 0, "burn: zero lp");
bytes memory data = abi.encodeWithSignature(
"burn(address,address,uint256,uint256)",
payer,
receiver,
lpAmount,
deadline
);
uint256 supply = totalSupply();
require(supply > 0, "burn: empty supply");
require(lmsr.nAssets > 0, "burn: uninit pool");
require(balanceOf(payer) >= lpAmount, "burn: insufficient LP");
// Refresh cached balances to reflect current on-chain balances before computing withdrawal amounts
for (uint i = 0; i < n; ) {
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
cachedUintBalances[i] = bal;
unchecked { i++; }
}
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
uint256[] memory withdrawAmounts = _burnReceiveAmounts(lpAmount);
// Transfer underlying tokens out to receiver according to computed proportions
for (uint i = 0; i < n; ) {
if (withdrawAmounts[i] > 0) {
tokens[i].safeTransfer(receiver, withdrawAmounts[i]);
}
unchecked { i++; }
}
// Update cached balances and internal q for all assets
int128[] memory newQInternal = new int128[](n);
for (uint i = 0; i < n; ) {
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
cachedUintBalances[i] = bal;
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
unchecked { i++; }
}
// 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) {
lmsr.deinit();
} else {
lmsr.updateForProportionalChange(newQInternal);
}
// Burn exactly the requested LP amount from payer (authorization via allowance)
if (msg.sender != payer) {
uint256 allowed = allowance(payer, msg.sender);
require(allowed >= lpAmount, "burn: allowance insufficient");
_approve(payer, msg.sender, allowed - lpAmount);
}
_burn(payer, lpAmount);
emit Burn(payer, receiver, withdrawAmounts, lpAmount);
Address.functionDelegateCall(mintImpl, data);
}
/* ----------------------