per-asset fees
This commit is contained in:
@@ -54,9 +54,11 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
int128 private immutable KAPPA; // kappa in Q64.64
|
||||
function kappa() external view returns (int128) { return KAPPA; }
|
||||
|
||||
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
||||
uint256 private immutable SWAP_FEE_PPM;
|
||||
function swapFeePpm() external view returns (uint256) { return SWAP_FEE_PPM; }
|
||||
/// @notice Per-asset swap fees in ppm.
|
||||
function fees() external view returns (uint256[] memory) { return _fees; }
|
||||
|
||||
/// @notice Effective combined fee in ppm for (i as input, j as output)
|
||||
function fee(uint256 i, uint256 j) external view returns (uint256) { return _pairFeePpm(i,j); }
|
||||
|
||||
/// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
|
||||
uint256 private immutable FLASH_FEE_PPM;
|
||||
@@ -100,7 +102,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @param symbol_ LP token symbol
|
||||
/// @param tokens_ token addresses (n)
|
||||
/// @param kappa_ liquidity parameter κ (Q64.64) used to derive b = κ * S(q)
|
||||
/// @param swapFeePpm_ fee in parts-per-million, taken from swap input amounts before LMSR calculations
|
||||
/// @param fees_ per-asset swap fees in ppm (length must equal tokens_.length)
|
||||
/// @param flashFeePpm_ fee in parts-per-million, taken for flash loans
|
||||
/// @param swapImpl_ address of the SwapMint implementation contract
|
||||
/// @param mintImpl_ address of the Mint implementation contract
|
||||
@@ -110,7 +112,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256 swapFeePpm_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
@@ -126,11 +128,17 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
require(tokens_.length > 1, "Pool: need >1 asset");
|
||||
_tokens = tokens_;
|
||||
KAPPA = kappa_;
|
||||
require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm");
|
||||
SWAP_FEE_PPM = swapFeePpm_;
|
||||
require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm");
|
||||
require(fees_.length == tokens_.length, "Pool: fees length");
|
||||
// validate ppm bounds and assign
|
||||
_fees = new uint256[](fees_.length);
|
||||
for (uint256 i = 0; i < fees_.length; i++) {
|
||||
// Cap all fees at 1%
|
||||
require(fees_[i] < 10_000, "Pool: fee >= 1%");
|
||||
_fees[i] = fees_[i];
|
||||
}
|
||||
require(flashFeePpm_ < 10_000, "Pool: flash fee >= 1%");
|
||||
FLASH_FEE_PPM = flashFeePpm_;
|
||||
require(protocolFeePpm_ < 1_000_000, "Pool: protocol fee >= ppm");
|
||||
require(protocolFeePpm_ < 400_000, "Pool: protocol fee >= 40%");
|
||||
// If the protocolFeePpm_ is set, then also require the fee address to be nonzero
|
||||
require(protocolFeePpm_ == 0 || protocolFeeAddress_ != address(0));
|
||||
PROTOCOL_FEE_PPM = protocolFeePpm_;
|
||||
@@ -168,8 +176,10 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @notice If a security problem is found, the vault owner may call this function to permanently disable swap and
|
||||
/// mint functionality, leaving only burns (withdrawals) working.
|
||||
function kill() external onlyOwner {
|
||||
_killed = true;
|
||||
emit Killed();
|
||||
if( !_killed ) {
|
||||
_killed = true;
|
||||
emit Killed();
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
@@ -232,7 +242,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
@@ -247,7 +257,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
) external payable native nonReentrant killable returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
) external payable native nonReentrant killable returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
|
||||
|
||||
// Compute amounts using the same path as views
|
||||
@@ -303,10 +313,8 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (
|
||||
) internal view
|
||||
returns (
|
||||
uint256 grossIn,
|
||||
uint256 amountOutUint,
|
||||
int128 amountInInternalUsed,
|
||||
@@ -316,7 +324,8 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
)
|
||||
{
|
||||
// Estimate max net input (fee on gross rounded up, then subtract)
|
||||
(, uint256 netUintForSwap) = _computeFee(maxAmountIn, SWAP_FEE_PPM);
|
||||
uint256 pairFeePpm = _pairFeePpm(inputTokenIndex, outputTokenIndex);
|
||||
(, uint256 netUintForSwap) = _computeFee(maxAmountIn, pairFeePpm);
|
||||
|
||||
// Convert to internal (floor)
|
||||
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, _bases[inputTokenIndex]);
|
||||
@@ -332,8 +341,8 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
// Compute gross transfer including fee on the used input (ceil)
|
||||
feeUint = 0;
|
||||
grossIn = amountInUintNoFee;
|
||||
if (SWAP_FEE_PPM > 0) {
|
||||
feeUint = _ceilFee(amountInUintNoFee, SWAP_FEE_PPM);
|
||||
if (pairFeePpm > 0) {
|
||||
feeUint = _ceilFee(amountInUintNoFee, pairFeePpm);
|
||||
grossIn += feeUint;
|
||||
}
|
||||
|
||||
@@ -354,7 +363,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
) external payable returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
|
||||
) external payable returns (uint256 amountInUsed, uint256 amountOut, uint256 inFee) {
|
||||
bytes memory data = abi.encodeWithSelector(
|
||||
PartyPoolSwapImpl.swapToLimit.selector,
|
||||
payer,
|
||||
@@ -364,7 +373,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
limitPrice,
|
||||
deadline,
|
||||
unwrap,
|
||||
SWAP_FEE_PPM,
|
||||
_pairFeePpm(inputTokenIndex, outputTokenIndex),
|
||||
PROTOCOL_FEE_PPM
|
||||
);
|
||||
bytes memory result = Address.functionDelegateCall(address(SWAP_IMPL), data);
|
||||
@@ -379,14 +388,14 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @param inputTokenIndex index of the input token
|
||||
/// @param maxAmountIn maximum uint token input (inclusive of fee)
|
||||
/// @param deadline optional deadline
|
||||
/// @return lpMinted actual LP minted (uint)
|
||||
/// @return amountInUsed actual input used (uint256), lpMinted actual LP minted (uint256), inFee fee taken from the input (uint256)
|
||||
function swapMint(
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
uint256 deadline
|
||||
) external payable returns (uint256 lpMinted) {
|
||||
) external payable returns (uint256 amountInUsed, uint256 lpMinted, uint256 inFee) {
|
||||
bytes memory data = abi.encodeWithSelector(
|
||||
PartyPoolMintImpl.swapMint.selector,
|
||||
payer,
|
||||
@@ -394,12 +403,12 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
inputTokenIndex,
|
||||
maxAmountIn,
|
||||
deadline,
|
||||
SWAP_FEE_PPM,
|
||||
_assetFeePpm(inputTokenIndex),
|
||||
PROTOCOL_FEE_PPM
|
||||
);
|
||||
|
||||
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
||||
return abi.decode(result, (uint256));
|
||||
return abi.decode(result, (uint256, uint256, uint256));
|
||||
}
|
||||
|
||||
/// @notice Burn LP _tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
|
||||
@@ -426,7 +435,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
outputTokenIndex,
|
||||
deadline,
|
||||
unwrap,
|
||||
SWAP_FEE_PPM,
|
||||
_assetFeePpm(outputTokenIndex),
|
||||
PROTOCOL_FEE_PPM
|
||||
);
|
||||
|
||||
@@ -435,13 +444,11 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
}
|
||||
|
||||
|
||||
bytes32 internal constant FLASH_CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
|
||||
/**
|
||||
* @dev Loan `amount` _tokens to `receiver`, and takes it back plus a `flashFee` after the callback.
|
||||
* @param receiver The contract receiving the _tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
|
||||
* @dev Loan `amount` tokens to `receiver`, and takes it back plus a `flashFee` after the callback.
|
||||
* @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
|
||||
* @param tokenAddr The loan currency.
|
||||
* @param amount The amount of _tokens lent.
|
||||
* @param amount The amount of tokens lent.
|
||||
* @param data A data parameter to be passed on to the `receiver` for any custom use.
|
||||
*/
|
||||
function flashLoan(
|
||||
@@ -449,37 +456,19 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
address tokenAddr,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external nonReentrant killable returns (bool)
|
||||
) external returns (bool)
|
||||
{
|
||||
IERC20 token = IERC20(tokenAddr);
|
||||
require(amount <= token.balanceOf(address(this)));
|
||||
uint256 tokenIndex = _tokenAddressToIndexPlusOne[token];
|
||||
require(tokenIndex != 0, 'flash: token not in pool');
|
||||
tokenIndex -= 1;
|
||||
(uint256 fee, ) = _computeFee(amount, FLASH_FEE_PPM);
|
||||
|
||||
// Compute protocol share of flash fee
|
||||
uint256 protoShare = 0;
|
||||
if (PROTOCOL_FEE_PPM > 0 && fee > 0) {
|
||||
protoShare = (fee * PROTOCOL_FEE_PPM) / 1_000_000; // floor
|
||||
if (protoShare > 0) {
|
||||
_protocolFeesOwed[tokenIndex] += protoShare;
|
||||
}
|
||||
}
|
||||
|
||||
_sendTokenTo(token, address(receiver), amount, false);
|
||||
require(receiver.onFlashLoan(msg.sender, address(token), amount, fee, data) == FLASH_CALLBACK_SUCCESS);
|
||||
_receiveTokenFrom(address(receiver), token, amount + fee);
|
||||
|
||||
// Update cached balance for the borrowed token
|
||||
uint256 balAfter = token.balanceOf(address(this));
|
||||
// Inline _recordCachedBalance logic
|
||||
require(balAfter >= _protocolFeesOwed[tokenIndex], "balance < protocol owed");
|
||||
_cachedUintBalances[tokenIndex] = balAfter - _protocolFeesOwed[tokenIndex];
|
||||
|
||||
emit Flash(msg.sender, receiver, token, amount, fee-protoShare, protoShare);
|
||||
|
||||
return true;
|
||||
bytes memory payload = abi.encodeWithSelector(
|
||||
PartyPoolSwapImpl.flashLoan.selector,
|
||||
receiver,
|
||||
tokenAddr,
|
||||
amount,
|
||||
data,
|
||||
FLASH_FEE_PPM,
|
||||
PROTOCOL_FEE_PPM
|
||||
);
|
||||
bytes memory result = Address.functionDelegateCall(address(SWAP_IMPL), payload);
|
||||
return abi.decode(result, (bool));
|
||||
}
|
||||
|
||||
|
||||
@@ -490,7 +479,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
PartyPoolSwapImpl.collectProtocolFees.selector,
|
||||
protocolFeeAddress
|
||||
);
|
||||
Address.functionDelegateCall(address(MINT_IMPL), data);
|
||||
Address.functionDelegateCall(address(SWAP_IMPL), data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user