482 lines
21 KiB
Solidity
482 lines
21 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
pragma solidity ^0.8.30;
|
|
|
|
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
|
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
|
|
import {Address} from "../lib/openzeppelin-contracts/contracts/utils/Address.sol";
|
|
import {ERC20External} from "./ERC20External.sol";
|
|
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
|
import {IPartyFlashCallback} from "./IPartyFlashCallback.sol";
|
|
import {IPartyPool} from "./IPartyPool.sol";
|
|
import {LMSRStabilizedBalancedPair} from "./LMSRStabilizedBalancedPair.sol";
|
|
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
|
import {PartyPoolBase} from "./PartyPoolBase.sol";
|
|
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
|
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
|
import {Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/Proxy.sol";
|
|
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
|
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import {IERC3156FlashLender} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol";
|
|
|
|
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
|
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
|
/// The pool issues an ERC20 LP token representing proportional ownership.
|
|
/// It supports:
|
|
/// - Proportional minting and burning of LP tokens,
|
|
/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap),
|
|
/// - Exact-input swaps and swaps-to-price-limits,
|
|
/// - Flash loans via a callback interface.
|
|
///
|
|
/// @dev The contract stores per-token uint "bases" used to scale token units into the internal Q64.64
|
|
/// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf calls.
|
|
/// The contract uses ceiling/floor rules described in function comments to bias rounding in favor of the pool
|
|
/// (i.e., floor outputs to users, ceil inputs/fees where appropriate).
|
|
contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
|
using ABDKMath64x64 for int128;
|
|
using LMSRStabilized for LMSRStabilized.State;
|
|
using SafeERC20 for IERC20;
|
|
|
|
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
|
/// @dev Pool is constructed with a fixed κ. Clients that previously passed tradeFrac/targetSlippage
|
|
/// should use LMSRStabilized.computeKappaFromSlippage(...) to derive κ and pass it here.
|
|
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 Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
|
|
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; }
|
|
|
|
// @inheritdoc IPartyPool
|
|
function allProtocolFeesOwed() external view returns (uint256[] memory) { return protocolFeesOwed; }
|
|
|
|
/// @notice Address of the Mint implementation contract for delegatecall
|
|
PartyPoolMintImpl private immutable MINT_IMPL;
|
|
function mintImpl() external view returns (PartyPoolMintImpl) { return MINT_IMPL; }
|
|
|
|
/// @notice Address of the SwapMint implementation contract for delegatecall
|
|
PartyPoolSwapImpl private immutable SWAP_IMPL;
|
|
function swapMintImpl() external view returns (PartyPoolSwapImpl) { return SWAP_IMPL; }
|
|
|
|
|
|
/// @inheritdoc IPartyPool
|
|
function getToken(uint256 i) external view returns (IERC20) { return tokens[i]; }
|
|
|
|
/// @inheritdoc IPartyPool
|
|
function numTokens() external view returns (uint256) { return tokens.length; }
|
|
|
|
/// @inheritdoc IPartyPool
|
|
function allTokens() external view returns (IERC20[] memory) { return tokens; }
|
|
|
|
/// @inheritdoc IPartyPool
|
|
function denominators() external view returns (uint256[] memory) { return bases; }
|
|
|
|
function LMSR() external view returns (LMSRStabilized.State memory) { return lmsr; }
|
|
|
|
|
|
/// @param name_ LP token name
|
|
/// @param symbol_ LP token symbol
|
|
/// @param tokens_ token addresses (n)
|
|
/// @param bases_ scaling bases for each token (n) - used when converting to/from internal 64.64 amounts
|
|
/// @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 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
|
|
constructor(
|
|
string memory name_,
|
|
string memory symbol_,
|
|
IERC20[] memory tokens_,
|
|
uint256[] memory bases_,
|
|
int128 kappa_,
|
|
uint256 swapFeePpm_,
|
|
uint256 flashFeePpm_,
|
|
uint256 protocolFeePpm_, // NEW: protocol share of fees (ppm)
|
|
address protocolFeeAddress_, // NEW: recipient for collected protocol tokens
|
|
PartyPoolSwapImpl swapImpl_,
|
|
PartyPoolMintImpl mintImpl_
|
|
) ERC20External(name_, symbol_) {
|
|
require(tokens_.length > 1, "Pool: need >1 asset");
|
|
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
|
|
tokens = tokens_;
|
|
bases = bases_;
|
|
KAPPA = kappa_;
|
|
require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm");
|
|
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_;
|
|
SWAP_IMPL = swapImpl_;
|
|
MINT_IMPL = mintImpl_;
|
|
|
|
uint256 n = tokens_.length;
|
|
|
|
// Initialize LMSR state nAssets; full init occurs on first mint when quantities are known.
|
|
lmsr.nAssets = n;
|
|
|
|
// Initialize token address to index mapping
|
|
for (uint i = 0; i < n;) {
|
|
tokenAddressToIndexPlusOne[tokens_[i]] = i + 1;
|
|
unchecked {i++;}
|
|
}
|
|
|
|
// Initialize caches to zero and protocol ledger
|
|
cachedUintBalances = new uint256[](n);
|
|
protocolFeesOwed = new uint256[](n);
|
|
}
|
|
|
|
|
|
/* ----------------------
|
|
Initialization / Mint / Burn (LP token managed)
|
|
---------------------- */
|
|
|
|
/// @inheritdoc IPartyPool
|
|
function initialMint(address receiver, uint256 lpTokens) external nonReentrant
|
|
returns (uint256 lpMinted) {
|
|
bytes memory data = abi.encodeWithSignature(
|
|
"initialMint(address,uint256,int128)",
|
|
receiver,
|
|
lpTokens,
|
|
KAPPA
|
|
);
|
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
|
return abi.decode(result, (uint256));
|
|
}
|
|
|
|
/// @notice Proportional mint for existing pool.
|
|
/// @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) {
|
|
bytes memory data = abi.encodeWithSignature(
|
|
"mint(address,address,uint256,uint256)",
|
|
payer,
|
|
receiver,
|
|
lpTokenAmount,
|
|
deadline
|
|
);
|
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
|
return abi.decode(result, (uint256));
|
|
}
|
|
|
|
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
|
|
/// @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
|
|
returns (uint256[] memory withdrawAmounts) {
|
|
bytes memory data = abi.encodeWithSignature(
|
|
"burn(address,address,uint256,uint256)",
|
|
payer,
|
|
receiver,
|
|
lpAmount,
|
|
deadline
|
|
);
|
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
|
return abi.decode(result, (uint256[]));
|
|
}
|
|
|
|
/* ----------------------
|
|
Swaps
|
|
---------------------- */
|
|
|
|
/*
|
|
function swapAmounts(
|
|
uint256 inputTokenIndex,
|
|
uint256 outputTokenIndex,
|
|
uint256 maxAmountIn,
|
|
int128 limitPrice
|
|
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
|
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
|
return (grossIn, outUint, feeUint);
|
|
}
|
|
*/
|
|
|
|
/// @inheritdoc IPartyPool
|
|
function swap(
|
|
address payer,
|
|
address receiver,
|
|
uint256 inputTokenIndex,
|
|
uint256 outputTokenIndex,
|
|
uint256 maxAmountIn,
|
|
int128 limitPrice,
|
|
uint256 deadline
|
|
) external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
|
uint256 n = tokens.length;
|
|
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
|
|
require(maxAmountIn > 0, "swap: input zero");
|
|
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
|
|
|
|
// Read previous balances for affected assets
|
|
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
uint256 prevBalJ = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
|
|
|
// Compute amounts using the same path as views
|
|
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) =
|
|
_quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
|
|
|
// Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer)
|
|
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransferAmount);
|
|
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
|
|
|
|
// Transfer output to receiver and verify exact decrease
|
|
tokens[outputTokenIndex].safeTransfer(receiver, amountOutUint);
|
|
uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
|
require(balJAfter == prevBalJ - amountOutUint, "swap: non-standard tokenOut");
|
|
|
|
// 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);
|
|
|
|
emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], totalTransferAmount, amountOutUint);
|
|
|
|
return (totalTransferAmount, amountOutUint, feeUint);
|
|
}
|
|
|
|
/// @notice Internal quote for exact-input swap that mirrors swap() rounding and fee application
|
|
/// @dev Returns amounts consistent with swap() semantics: grossIn includes fees (ceil), amountOut is floored.
|
|
/// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
|
|
/// amountInInternalUsed and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint),
|
|
/// feeUint fee taken from the gross input (uint)
|
|
function _quoteSwapExactIn(
|
|
uint256 inputTokenIndex,
|
|
uint256 outputTokenIndex,
|
|
uint256 maxAmountIn,
|
|
int128 limitPrice
|
|
)
|
|
internal
|
|
view
|
|
returns (
|
|
uint256 grossIn,
|
|
uint256 amountOutUint,
|
|
int128 amountInInternalUsed,
|
|
int128 amountOutInternal,
|
|
uint256 amountInUintNoFee,
|
|
uint256 feeUint
|
|
)
|
|
{
|
|
uint256 n = tokens.length;
|
|
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
|
|
require(maxAmountIn > 0, "swap: input zero");
|
|
require(lmsr.nAssets > 0, "swap: empty pool");
|
|
|
|
// Estimate max net input (fee on gross rounded up, then subtract)
|
|
(, uint256 netUintForSwap) = _computeFee(maxAmountIn, SWAP_FEE_PPM);
|
|
|
|
// Convert to internal (floor)
|
|
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[inputTokenIndex]);
|
|
require(deltaInternalI > int128(0), "swap: input too small after fee");
|
|
|
|
// Compute internal amounts using LMSR (exact-input with price limit)
|
|
(amountInInternalUsed, amountOutInternal) = _swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
|
|
|
|
// Convert actual used input internal -> uint (ceil)
|
|
amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, bases[inputTokenIndex]);
|
|
require(amountInUintNoFee > 0, "swap: input zero");
|
|
|
|
// 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);
|
|
grossIn += feeUint;
|
|
}
|
|
|
|
// Ensure within user max
|
|
require(grossIn <= maxAmountIn, "swap: transfer exceeds max");
|
|
|
|
// Compute output (floor)
|
|
amountOutUint = _internalToUintFloor(amountOutInternal, bases[outputTokenIndex]);
|
|
require(amountOutUint > 0, "swap: output zero");
|
|
}
|
|
|
|
|
|
/// @inheritdoc IPartyPool
|
|
function swapToLimit(
|
|
address payer,
|
|
address receiver,
|
|
uint256 inputTokenIndex,
|
|
uint256 outputTokenIndex,
|
|
int128 limitPrice,
|
|
uint256 deadline
|
|
) external nonReentrant returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
|
|
bytes memory data = abi.encodeWithSignature(
|
|
'swapToLimit(address,address,uint256,uint256,int128,uint256,uint256,uint256)',
|
|
payer,
|
|
receiver,
|
|
inputTokenIndex,
|
|
outputTokenIndex,
|
|
limitPrice,
|
|
deadline,
|
|
SWAP_FEE_PPM,
|
|
PROTOCOL_FEE_PPM
|
|
);
|
|
bytes memory result = Address.functionDelegateCall(address(SWAP_IMPL), data);
|
|
return abi.decode(result, (uint256,uint256,uint256));
|
|
}
|
|
|
|
|
|
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
|
/// @dev This function forwards the call to the swapMint implementation via delegatecall
|
|
/// @param payer who transfers the input token
|
|
/// @param receiver who receives the minted LP tokens
|
|
/// @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)
|
|
function swapMint(
|
|
address payer,
|
|
address receiver,
|
|
uint256 inputTokenIndex,
|
|
uint256 maxAmountIn,
|
|
uint256 deadline
|
|
) external returns (uint256 lpMinted) {
|
|
bytes memory data = abi.encodeWithSignature(
|
|
"swapMint(address,address,uint256,uint256,uint256,uint256,uint256)",
|
|
payer,
|
|
receiver,
|
|
inputTokenIndex,
|
|
maxAmountIn,
|
|
deadline,
|
|
SWAP_FEE_PPM,
|
|
PROTOCOL_FEE_PPM
|
|
);
|
|
|
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
|
return abi.decode(result, (uint256));
|
|
}
|
|
|
|
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
|
|
/// @dev This function forwards the call to the burnSwap implementation via delegatecall
|
|
/// @param payer who burns LP tokens
|
|
/// @param receiver who receives the single asset
|
|
/// @param lpAmount amount of LP tokens to burn
|
|
/// @param inputTokenIndex index of target asset to receive
|
|
/// @param deadline optional deadline
|
|
/// @return amountOutUint uint amount of asset i sent to receiver
|
|
function burnSwap(
|
|
address payer,
|
|
address receiver,
|
|
uint256 lpAmount,
|
|
uint256 inputTokenIndex,
|
|
uint256 deadline
|
|
) external returns (uint256 amountOutUint) {
|
|
bytes memory data = abi.encodeWithSignature(
|
|
"burnSwap(address,address,uint256,uint256,uint256,uint256,uint256)",
|
|
payer,
|
|
receiver,
|
|
lpAmount,
|
|
inputTokenIndex,
|
|
deadline,
|
|
SWAP_FEE_PPM,
|
|
PROTOCOL_FEE_PPM
|
|
);
|
|
|
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
|
return abi.decode(result, (uint256));
|
|
}
|
|
|
|
|
|
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.
|
|
* @param tokenAddr The loan currency.
|
|
* @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(
|
|
IERC3156FlashBorrower receiver,
|
|
address tokenAddr,
|
|
uint256 amount,
|
|
bytes calldata data
|
|
) external nonReentrant returns (bool)
|
|
{
|
|
IERC20 token = IERC20(tokenAddr);
|
|
require(amount <= token.balanceOf(address(this)));
|
|
(uint256 fee, ) = _computeFee(amount, FLASH_FEE_PPM);
|
|
require(
|
|
token.transfer(address(receiver), amount),
|
|
"FlashLender: Transfer failed"
|
|
);
|
|
require(
|
|
receiver.onFlashLoan(msg.sender, address(token), amount, fee, data) == FLASH_CALLBACK_SUCCESS,
|
|
"FlashLender: Callback failed"
|
|
);
|
|
require(
|
|
token.transferFrom(address(receiver), address(this), amount + fee),
|
|
"FlashLender: Repay failed"
|
|
);
|
|
return true;
|
|
}
|
|
|
|
|
|
/// @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;
|
|
}
|
|
}
|
|
|
|
function _swapAmountsForExactInput(uint256 i, uint256 j, int128 a, int128 limitPrice) internal virtual view
|
|
returns (int128 amountIn, int128 amountOut) {
|
|
return lmsr.swapAmountsForExactInput(i, j, a, limitPrice);
|
|
}
|
|
|
|
/// @notice Compute fee and net amounts for a gross input (fee rounded up to favor the pool).
|
|
/// @return feeUint fee taken (uint) and netUint remaining for protocol use (uint)
|
|
function _computeFee(uint256 gross) internal view returns (uint256 feeUint, uint256 netUint) {
|
|
return _computeFee(gross, SWAP_FEE_PPM);
|
|
}
|
|
|
|
/// @notice Convenience: return gross = net + fee(net) using ceiling for fee.
|
|
function _addFee(uint256 netUint) internal view returns (uint256 gross) {
|
|
return _addFee(netUint, SWAP_FEE_PPM);
|
|
}
|
|
|
|
}
|