protocol fees

This commit is contained in:
tim
2025-10-02 16:43:02 -04:00
parent b7e1b1cac2
commit c002d26daf
5 changed files with 178 additions and 22 deletions

View File

@@ -46,6 +46,14 @@ contract PartyPool is PartyPoolBase, IPartyPool {
uint256 private immutable FLASH_FEE_PPM;
function flashFeePpm() external view returns (uint256) { return FLASH_FEE_PPM; }
/// @notice Protocol fee share (ppm) applied to fees collected by the pool (floored when accrued)
uint256 private immutable PROTOCOL_FEE_PPM;
function protocolFeePpm() external view returns (uint256) { return PROTOCOL_FEE_PPM; }
/// @notice Address to which collected protocol tokens will be sent on collectProtocolFees()
address private immutable PROTOCOL_FEE_ADDRESS;
function protocolFeeAddress() external view returns (address) { return PROTOCOL_FEE_ADDRESS; }
/// @notice If true and there are exactly two assets, an optimized 2-asset stable-pair path is used for some computations.
bool immutable private IS_STABLE_PAIR; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled
@@ -88,6 +96,8 @@ contract PartyPool is PartyPoolBase, IPartyPool {
int128 kappa_,
uint256 swapFeePpm_,
uint256 flashFeePpm_,
uint256 protocolFeePpm_, // NEW: protocol share of fees (ppm)
address protocolFeeAddress_, // NEW: recipient for collected protocol tokens
bool stable_,
PartyPoolSwapMintImpl swapMintImpl_,
PartyPoolMintImpl mintImpl_
@@ -101,6 +111,9 @@ contract PartyPool is PartyPoolBase, IPartyPool {
SWAP_FEE_PPM = swapFeePpm_;
require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm");
FLASH_FEE_PPM = flashFeePpm_;
require(protocolFeePpm_ < 1_000_000, "Pool: protocol fee >= ppm");
PROTOCOL_FEE_PPM = protocolFeePpm_;
PROTOCOL_FEE_ADDRESS = protocolFeeAddress_;
IS_STABLE_PAIR = stable_ && tokens_.length == 2;
SWAP_MINT_IMPL = swapMintImpl_;
MINT_IMPL = mintImpl_;
@@ -116,8 +129,9 @@ contract PartyPool is PartyPoolBase, IPartyPool {
unchecked {i++;}
}
// Initialize caches to zero
// Initialize caches to zero and protocol ledger
cachedUintBalances = new uint256[](n);
protocolFeesOwed = new uint256[](n);
}
@@ -212,6 +226,36 @@ contract PartyPool is PartyPoolBase, IPartyPool {
}
// Per-token owed protocol fees (raw token units). Public getter autogenerated.
uint256[] public protocolFeesOwed;
/// @notice Transfer all protocol fees to the configured protocolFeeAddress and zero the ledger.
/// @dev Anyone can call; must have protocolFeeAddress != address(0) to be operational.
function collectProtocolFees() external nonReentrant {
address dest = PROTOCOL_FEE_ADDRESS;
require(dest != address(0), "collect: zero addr");
uint256 n = tokens.length;
for (uint256 i = 0; i < n; i++) {
uint256 owed = protocolFeesOwed[i];
if (owed == 0) continue;
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
require(bal >= owed, "collect: fee > bal");
protocolFeesOwed[i] = 0;
// transfer owed tokens to protocol destination
tokens[i].safeTransfer(dest, owed);
// update cached to effective onchain minus owed
cachedUintBalances[i] = bal - owed;
}
}
/// @dev Helper to record cached balances as effectiveBalance = onchain - owed. Reverts if owed > onchain.
function _recordCachedBalance(uint256 idx, uint256 onchainBal) internal {
uint256 owed = protocolFeesOwed[idx];
require(onchainBal >= owed, "balance < protocol owed");
cachedUintBalances[idx] = onchainBal - owed;
}
/// @notice Swap input token i -> token j. Payer must approve token i.
/// @dev This function transfers the exact gross input (including fee) from payer and sends the computed output to receiver.
/// Non-standard tokens (fee-on-transfer, rebasers) are rejected via balance checks.
@@ -255,9 +299,17 @@ contract PartyPool is PartyPoolBase, IPartyPool {
uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
require(balJAfter == prevBalJ - amountOutUint, "swap: non-standard tokenOut");
// Update cached uint balances for i and j using actual balances
cachedUintBalances[inputTokenIndex] = balIAfter;
cachedUintBalances[outputTokenIndex] = balJAfter;
// Accrue protocol share (floor) from the fee on input token
if (PROTOCOL_FEE_PPM > 0 && feeUint > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
uint256 protoShare = (feeUint * PROTOCOL_FEE_PPM) / 1_000_000; // floor
if (protoShare > 0) {
protocolFeesOwed[inputTokenIndex] += protoShare;
}
}
// Update cached uint balances for i and j using effective balances (onchain - owed)
_recordCachedBalance(inputTokenIndex, balIAfter);
_recordCachedBalance(outputTokenIndex, balJAfter);
// Apply swap to LMSR state with the internal amounts actually used
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalUsed, amountOutInternal);
@@ -302,9 +354,17 @@ contract PartyPool is PartyPoolBase, IPartyPool {
uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
require(balJAfter == prevBalJ - amountOutUint, "swapToLimit: non-standard tokenOut");
// Update caches to actual balances
cachedUintBalances[inputTokenIndex] = balIAfter;
cachedUintBalances[outputTokenIndex] = balJAfter;
// Accrue protocol share (floor) from the fee on input token
if (PROTOCOL_FEE_PPM > 0 && feeUint > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
uint256 protoShare = (feeUint * PROTOCOL_FEE_PPM) / 1_000_000; // floor
if (protoShare > 0) {
protocolFeesOwed[inputTokenIndex] += protoShare;
}
}
// Update caches to effective balances
_recordCachedBalance(inputTokenIndex, balIAfter);
_recordCachedBalance(outputTokenIndex, balJAfter);
// Apply swap to LMSR state with the internal amounts
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalMax, amountOutInternal);
@@ -487,7 +547,22 @@ contract PartyPool is PartyPoolBase, IPartyPool {
);
bytes memory result = Address.functionDelegateCall(address(SWAP_MINT_IMPL), data);
return abi.decode(result, (uint256));
// New ABI: implementation returns (uint256 lpMinted, uint256 feeUintActual)
(uint256 lpOut, uint256 feeUintActual) = abi.decode(result, (uint256, uint256));
// Accrue protocol share (floor) from the fee on the input token
if (PROTOCOL_FEE_PPM > 0 && feeUintActual > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
uint256 protoShare = (feeUintActual * PROTOCOL_FEE_PPM) / 1_000_000;
if (protoShare > 0) {
protocolFeesOwed[inputTokenIndex] += protoShare;
}
}
// Update cached balance for the input token to effective onchain - owed
uint256 bal = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
_recordCachedBalance(inputTokenIndex, bal);
return lpOut;
}
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
@@ -516,7 +591,22 @@ contract PartyPool is PartyPoolBase, IPartyPool {
);
bytes memory result = Address.functionDelegateCall(address(SWAP_MINT_IMPL), data);
return abi.decode(result, (uint256));
// New ABI: implementation returns (uint256 amountOutUint, uint256 feeTokenUint)
(uint256 outAmt, uint256 feeTokenUint) = abi.decode(result, (uint256, uint256));
// Accrue protocol share (floor) from the token-side fee computed by implementation
if (PROTOCOL_FEE_PPM > 0 && feeTokenUint > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
uint256 protoShare = (feeTokenUint * PROTOCOL_FEE_PPM) / 1_000_000;
if (protoShare > 0) {
protocolFeesOwed[inputTokenIndex] += protoShare;
}
}
// Update cached balance for the target token to effective onchain - owed
uint256 bal = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
_recordCachedBalance(inputTokenIndex, bal);
return outAmt;
}
@@ -588,14 +678,25 @@ contract PartyPool is PartyPoolBase, IPartyPool {
if (amounts[i] > 0) {
uint256 currentBalance = IERC20(tokens[i]).balanceOf(address(this));
// Compute expected fee (ceiling)
uint256 feeExpected = _ceilFee(amounts[i], FLASH_FEE_PPM);
// Verify repayment: current balance must be at least (initial balance + fee)
require(
currentBalance >= initialBalances[i] + _ceilFee(amounts[i], FLASH_FEE_PPM),
currentBalance >= initialBalances[i] + feeExpected,
"flash: repayment failed"
);
// Update cached balance
cachedUintBalances[i] = currentBalance;
// Accrue protocol share (floor) of the flash fee
if (PROTOCOL_FEE_PPM > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
uint256 protoShare = (feeExpected * PROTOCOL_FEE_PPM) / 1_000_000; // floor
if (protoShare > 0) {
protocolFeesOwed[i] += protoShare;
}
}
// Update cached balance to onchain minus owed
_recordCachedBalance(i, currentBalance);
}
}
}