protocol fees
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user