PartyPlanner; chain.json
This commit is contained in:
84
src/IPartyPlanner.sol
Normal file
84
src/IPartyPlanner.sol
Normal file
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "./PartyPool.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
/// @title IPartyPlanner
|
||||
/// @notice Interface for factory contract for creating and tracking PartyPool instances
|
||||
interface IPartyPlanner {
|
||||
// Event emitted when a new pool is created
|
||||
event PartyStarted(PartyPool indexed pool, string name, string symbol, IERC20[] tokens);
|
||||
|
||||
/// @notice Creates a new PartyPool instance and initializes it with initial deposits
|
||||
/// @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 _tradeFrac trade fraction in 64.64 fixed-point (as used by LMSR)
|
||||
/// @param _targetSlippage target slippage in 64.64 fixed-point (as used by LMSR)
|
||||
/// @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 _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated
|
||||
/// @param payer address that provides the initial token deposits
|
||||
/// @param receiver address that receives the minted LP tokens
|
||||
/// @param initialDeposits amounts of each token to deposit initially
|
||||
/// @param deadline Reverts if nonzero and the current blocktime is later than the deadline
|
||||
/// @return pool Address of the newly created and initialized PartyPool
|
||||
/// @return lpAmount Amount of LP tokens minted to the receiver
|
||||
function createPool(
|
||||
// Pool constructor args
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory _tokens,
|
||||
uint256[] memory _bases,
|
||||
int128 _tradeFrac,
|
||||
int128 _targetSlippage,
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
bool _stable,
|
||||
// Initial deposit information
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256[] memory initialDeposits,
|
||||
uint256 initialLpAmount,
|
||||
uint256 deadline
|
||||
) external returns (PartyPool pool, uint256 lpAmount);
|
||||
|
||||
/// @notice Checks if a pool is supported
|
||||
/// @param pool The pool address to check
|
||||
/// @return bool True if the pool is supported, false otherwise
|
||||
function getPoolSupported(address pool) external view returns (bool);
|
||||
|
||||
/// @notice Returns the total number of pools created
|
||||
/// @return The total count of pools
|
||||
function poolCount() external view returns (uint256);
|
||||
|
||||
/// @notice Retrieves a page of pool addresses
|
||||
/// @param offset Starting index for pagination
|
||||
/// @param limit Maximum number of items to return
|
||||
/// @return pools Array of pool addresses for the requested page
|
||||
function getAllPools(uint256 offset, uint256 limit) external view returns (PartyPool[] memory pools);
|
||||
|
||||
/// @notice Returns the total number of unique tokens
|
||||
/// @return The total count of unique tokens
|
||||
function tokenCount() external view returns (uint256);
|
||||
|
||||
/// @notice Retrieves a page of token addresses
|
||||
/// @param offset Starting index for pagination
|
||||
/// @param limit Maximum number of items to return
|
||||
/// @return tokens Array of token addresses for the requested page
|
||||
function getAllTokens(uint256 offset, uint256 limit) external view returns (address[] memory tokens);
|
||||
|
||||
/// @notice Returns the total number of pools for a specific token
|
||||
/// @param token The token address to query
|
||||
/// @return The total count of pools containing the token
|
||||
function poolsByTokenCount(IERC20 token) external view returns (uint256);
|
||||
|
||||
/// @notice Retrieves a page of pool addresses for a specific token
|
||||
/// @param token The token address to query pools for
|
||||
/// @param offset Starting index for pagination
|
||||
/// @param limit Maximum number of items to return
|
||||
/// @return pools Array of pool addresses containing the specified token
|
||||
function getPoolsByToken(IERC20 token, uint256 offset, uint256 limit) external view returns (PartyPool[] memory pools);
|
||||
}
|
||||
@@ -28,8 +28,8 @@ interface IPartyPool is IERC20Metadata {
|
||||
event Swap(
|
||||
address payer,
|
||||
address indexed receiver,
|
||||
address indexed tokenIn,
|
||||
address indexed tokenOut,
|
||||
IERC20 indexed tokenIn,
|
||||
IERC20 indexed tokenOut,
|
||||
uint256 amountIn,
|
||||
uint256 amountOut
|
||||
);
|
||||
@@ -58,13 +58,13 @@ interface IPartyPool is IERC20Metadata {
|
||||
// Immutable pool configuration (public getters)
|
||||
/// @notice Token addresses comprising the pool. Effectively immutable after construction.
|
||||
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
|
||||
function tokens(uint256) external view returns (address); // get single token
|
||||
function tokens(uint256) external view returns (IERC20); // get single token
|
||||
|
||||
/// @notice Returns the number of tokens (n) in the pool.
|
||||
function numTokens() external view returns (uint256);
|
||||
|
||||
/// @notice Returns the list of all token addresses in the pool (copy).
|
||||
function allTokens() external view returns (address[] memory);
|
||||
function allTokens() external view returns (IERC20[] memory);
|
||||
|
||||
/// @notice Per-token uint base denominators used to convert uint token amounts <-> internal Q64.64 representation.
|
||||
/// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals.
|
||||
@@ -85,7 +85,7 @@ interface IPartyPool is IERC20Metadata {
|
||||
|
||||
/// @notice Mapping from token address => (index+1). A zero value indicates the token is not in the pool.
|
||||
/// @dev Use index = tokenAddressToIndexPlusOne[token] - 1 when non-zero.
|
||||
function tokenAddressToIndexPlusOne(address) external view returns (uint);
|
||||
function tokenAddressToIndexPlusOne(IERC20) external view returns (uint);
|
||||
|
||||
// Initialization / Mint / Burn (LP token managed)
|
||||
|
||||
@@ -105,7 +105,8 @@ interface IPartyPool is IERC20Metadata {
|
||||
/// @param receiver address that receives the LP tokens
|
||||
/// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit)
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external;
|
||||
/// @return lpMinted the actual amount of lpToken minted
|
||||
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external returns (uint256 lpMinted);
|
||||
|
||||
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount
|
||||
/// @dev Returns the maximum token amounts (rounded down) that will be withdrawn when burning lpTokenAmount.
|
||||
|
||||
@@ -183,9 +183,6 @@ library LMSRStabilizedBalancedPair {
|
||||
// Now compute a two-tier approximation using Horner-style evaluation to reduce mul/divs.
|
||||
// Primary tier (cheap quadratic): accurate for small u = a/b.
|
||||
// Secondary tier (cubic correction): used when u is moderate but still within U_MAX.
|
||||
int128 one = ONE;
|
||||
int128 HALF = ABDKMath64x64.divu(1, 2); // 0.5
|
||||
int128 THIRD = ABDKMath64x64.divu(1, 3); // ~0.333...
|
||||
|
||||
// Precomputed thresholds
|
||||
int128 U_TIER1 = ABDKMath64x64.divu(1, 10); // 0.1 -> cheap quadratic tier
|
||||
@@ -194,7 +191,7 @@ library LMSRStabilizedBalancedPair {
|
||||
// u is already computed above
|
||||
// Compute X = u*(1 + delta) - u^2/2
|
||||
int128 u2 = u.mul(u);
|
||||
int128 X = u.mul(one.add(delta)).sub(u2.div(ABDKMath64x64.fromUInt(2)));
|
||||
int128 X = u.mul(ONE.add(delta)).sub(u2.div(ABDKMath64x64.fromUInt(2)));
|
||||
|
||||
// Compute X^2 once
|
||||
int128 X2 = X.mul(X);
|
||||
|
||||
183
src/PartyPlanner.sol
Normal file
183
src/PartyPlanner.sol
Normal file
@@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "./IPartyPlanner.sol";
|
||||
import "./PartyPool.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
/// @title PartyPlanner
|
||||
/// @notice Factory contract for creating and tracking PartyPool instances
|
||||
contract PartyPlanner is IPartyPlanner {
|
||||
using SafeERC20 for IERC20;
|
||||
int128 private constant FIXED_ONE_64x64 = int128(1) << 64;
|
||||
|
||||
// On-chain pool indexing
|
||||
PartyPool[] private _allPools;
|
||||
IERC20[] private _allTokens;
|
||||
mapping(PartyPool => bool) private _poolSupported;
|
||||
mapping(IERC20 => bool) private _tokenSupported;
|
||||
mapping(IERC20 => PartyPool[]) private _poolsByToken;
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function createPool(
|
||||
// Pool constructor args
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory _tokens,
|
||||
uint256[] memory _bases,
|
||||
int128 _tradeFrac,
|
||||
int128 _targetSlippage,
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
bool _stable,
|
||||
// Initial deposit information
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256[] memory initialDeposits,
|
||||
uint256 initialLpAmount,
|
||||
uint256 deadline
|
||||
) external returns (PartyPool pool, uint256 lpAmount) {
|
||||
// Validate inputs
|
||||
require(deadline == 0 || block.timestamp <= deadline, "Planner: deadline exceeded");
|
||||
require(_tokens.length == initialDeposits.length, "Planner: tokens and deposits length mismatch");
|
||||
require(payer != address(0), "Planner: payer cannot be zero address");
|
||||
require(receiver != address(0), "Planner: receiver cannot be zero address");
|
||||
|
||||
// Validate fixed-point fractions: must be less than 1.0 in 64.64 fixed-point
|
||||
require(_tradeFrac < FIXED_ONE_64x64, "Planner: tradeFrac must be < 1 (64.64)");
|
||||
require(_targetSlippage < FIXED_ONE_64x64, "Planner: targetSlippage must be < 1 (64.64)");
|
||||
|
||||
// Create a new PartyPool instance
|
||||
pool = new PartyPool(
|
||||
name_,
|
||||
symbol_,
|
||||
_tokens,
|
||||
_bases,
|
||||
_tradeFrac,
|
||||
_targetSlippage,
|
||||
_swapFeePpm,
|
||||
_flashFeePpm,
|
||||
_stable
|
||||
);
|
||||
|
||||
_allPools.push(pool);
|
||||
_poolSupported[pool] = true;
|
||||
|
||||
// Track tokens and populate mappings
|
||||
for (uint256 i = 0; i < _tokens.length; i++) {
|
||||
IERC20 token = _tokens[i];
|
||||
|
||||
// Add token to _allTokens if not already present
|
||||
if (!_tokenSupported[token]) {
|
||||
_allTokens.push(token);
|
||||
_tokenSupported[token] = true;
|
||||
}
|
||||
|
||||
// Add pool to _poolsByToken mapping
|
||||
_poolsByToken[token].push(pool);
|
||||
}
|
||||
|
||||
emit PartyStarted(pool, name_, symbol_, _tokens);
|
||||
|
||||
// Transfer initial tokens from payer to the pool
|
||||
for (uint256 i = 0; i < _tokens.length; i++) {
|
||||
if (initialDeposits[i] > 0) {
|
||||
IERC20(_tokens[i]).safeTransferFrom(payer, address(pool), initialDeposits[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Call mint on the new pool to initialize it with the transferred tokens
|
||||
lpAmount = pool.initialMint(receiver, initialLpAmount);
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function getPoolSupported(address pool) external view returns (bool) {
|
||||
return _poolSupported[PartyPool(pool)];
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function poolCount() external view returns (uint256) {
|
||||
return _allPools.length;
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function getAllPools(uint256 offset, uint256 limit) external view returns (PartyPool[] memory pools) {
|
||||
uint256 totalPools = _allPools.length;
|
||||
|
||||
// If offset is beyond array bounds, return empty array
|
||||
if (offset >= totalPools) {
|
||||
return new PartyPool[](0);
|
||||
}
|
||||
|
||||
// Calculate actual number of pools to return (respecting bounds)
|
||||
uint256 itemsToReturn = (offset + limit > totalPools) ? (totalPools - offset) : limit;
|
||||
|
||||
// Create result array of appropriate size
|
||||
pools = new PartyPool[](itemsToReturn);
|
||||
|
||||
// Fill the result array
|
||||
for (uint256 i = 0; i < itemsToReturn; i++) {
|
||||
pools[i] = _allPools[offset + i];
|
||||
}
|
||||
|
||||
return pools;
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function tokenCount() external view returns (uint256) {
|
||||
return _allTokens.length;
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function getAllTokens(uint256 offset, uint256 limit) external view returns (address[] memory tokens) {
|
||||
uint256 totalTokens = _allTokens.length;
|
||||
|
||||
// If offset is beyond array bounds, return empty array
|
||||
if (offset >= totalTokens) {
|
||||
return new address[](0);
|
||||
}
|
||||
|
||||
// Calculate actual number of tokens to return (respecting bounds)
|
||||
uint256 itemsToReturn = (offset + limit > totalTokens) ? (totalTokens - offset) : limit;
|
||||
|
||||
// Create result array of appropriate size
|
||||
tokens = new address[](itemsToReturn);
|
||||
|
||||
// Fill the result array
|
||||
for (uint256 i = 0; i < itemsToReturn; i++) {
|
||||
tokens[i] = address(_allTokens[offset + i]);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function poolsByTokenCount(IERC20 token) external view returns (uint256) {
|
||||
return _poolsByToken[token].length;
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
function getPoolsByToken(IERC20 token, uint256 offset, uint256 limit) external view returns (PartyPool[] memory pools) {
|
||||
PartyPool[] storage tokenPools = _poolsByToken[token];
|
||||
uint256 totalPools = tokenPools.length;
|
||||
|
||||
// If offset is beyond array bounds, return empty array
|
||||
if (offset >= totalPools) {
|
||||
return new PartyPool[](0);
|
||||
}
|
||||
|
||||
// Calculate actual number of pools to return (respecting bounds)
|
||||
uint256 itemsToReturn = (offset + limit > totalPools) ? (totalPools - offset) : limit;
|
||||
|
||||
// Create result array of appropriate size
|
||||
pools = new PartyPool[](itemsToReturn);
|
||||
|
||||
// Fill the result array
|
||||
for (uint256 i = 0; i < itemsToReturn; i++) {
|
||||
pools[i] = tokenPools[offset + i];
|
||||
}
|
||||
|
||||
return pools;
|
||||
}
|
||||
}
|
||||
@@ -37,13 +37,13 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
|
||||
/// @notice Token addresses comprising the pool. Effectively immutable after construction.
|
||||
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
|
||||
address[] public tokens; // effectively immutable since there is no interface to change the tokens
|
||||
IERC20[] public tokens; // effectively immutable since there is no interface to change the tokens
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function numTokens() external view returns (uint256) { return tokens.length; }
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function allTokens() external view returns (address[] memory) { return tokens; }
|
||||
function allTokens() external view returns (IERC20[] memory) { return tokens; }
|
||||
|
||||
// NOTE that the slippage target is only exactly achieved in completely balanced pools where all assets are
|
||||
// priced the same. This target is actually a minimum slippage that the pool imposes on traders, and the actual
|
||||
@@ -84,7 +84,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
|
||||
/// @notice Mapping from token address => (index+1). A zero value indicates the token is not in the pool.
|
||||
/// @dev Use index = tokenAddressToIndexPlusOne[token] - 1 when non-zero.
|
||||
mapping(address=>uint) public tokenAddressToIndexPlusOne; // Uses index+1 so a result of 0 indicates a failed lookup
|
||||
mapping(IERC20=>uint) public tokenAddressToIndexPlusOne; // Uses index+1 so a result of 0 indicates a failed lookup
|
||||
|
||||
/// @notice Scale factor used when converting LMSR Q64.64 totals to LP token units (uint).
|
||||
/// @dev LP tokens are minted in units equal to ABDK.mulu(lastTotalQ64x64, LP_SCALE).
|
||||
@@ -102,7 +102,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
constructor(
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
address[] memory _tokens,
|
||||
IERC20[] memory _tokens,
|
||||
uint256[] memory _bases,
|
||||
int128 _tradeFrac,
|
||||
int128 _targetSlippage,
|
||||
@@ -168,6 +168,120 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
return depositAmounts;
|
||||
}
|
||||
|
||||
/// @notice Initial mint to set up pool for the first time.
|
||||
/// @dev Assumes tokens have already been transferred to the pool prior to calling.
|
||||
/// Can only be called when the pool is uninitialized (totalSupply() == 0 or lmsr.nAssets == 0).
|
||||
/// @param receiver address that receives the LP tokens
|
||||
/// @param lpTokens The number of LP tokens to issue for this mint. If 0, then the number of tokens returned will equal the LMSR internal q total
|
||||
function initialMint(address receiver, uint256 lpTokens) external nonReentrant
|
||||
returns (uint256 lpMinted) {
|
||||
uint256 n = tokens.length;
|
||||
|
||||
// Check if this is initial deposit - revert if not
|
||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||
require(isInitialDeposit, "initialMint: pool already initialized");
|
||||
|
||||
// Update cached balances for all assets
|
||||
int128[] memory newQInternal = new int128[](n);
|
||||
uint256[] memory depositAmounts = new uint256[](n);
|
||||
for (uint i = 0; i < n; ) {
|
||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||
cachedUintBalances[i] = bal;
|
||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||
depositAmounts[i] = bal;
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// Initialize the stabilized LMSR state
|
||||
lmsr.init(newQInternal, tradeFrac, targetSlippage);
|
||||
|
||||
// Compute actual LP tokens to mint based on size metric (scaled)
|
||||
if( lpTokens != 0 )
|
||||
lpMinted = lpTokens;
|
||||
else {
|
||||
int128 newTotal = _computeSizeMetric(newQInternal);
|
||||
lpMinted = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||
}
|
||||
|
||||
require(lpMinted > 0, "initialMint: zero LP amount");
|
||||
_mint(receiver, lpMinted);
|
||||
emit Mint(address(0), receiver, depositAmounts, lpMinted);
|
||||
}
|
||||
|
||||
/// @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).
|
||||
/// @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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
|
||||
return _burnReceiveAmounts(lpTokenAmount);
|
||||
@@ -194,106 +308,6 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
return withdrawAmounts;
|
||||
}
|
||||
|
||||
/// @notice Proportional mint (or initial supply if first call).
|
||||
/// @dev - For initial supply: assumes tokens have already been transferred to the pool prior to calling.
|
||||
/// - For subsequent mints: payer must approve the required token amounts before calling.
|
||||
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
|
||||
/// @param payer address that provides the input tokens (ignored for initial deposit)
|
||||
/// @param receiver address that receives the LP tokens
|
||||
/// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit)
|
||||
/// @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 {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
||||
uint256 n = tokens.length;
|
||||
// Check if this is initial deposit
|
||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||
|
||||
require(lpTokenAmount > 0 || isInitialDeposit, "mint: zero LP amount");
|
||||
|
||||
// Capture old pool size metric (scaled) by computing from current balances
|
||||
uint256 oldScaled = 0;
|
||||
if (!isInitialDeposit) {
|
||||
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
||||
oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||
}
|
||||
|
||||
// For non-initial deposits, transfer tokens from payer
|
||||
uint256[] memory depositAmounts = new uint256[](n);
|
||||
|
||||
if (!isInitialDeposit) {
|
||||
// Calculate required deposit amounts for the desired LP tokens
|
||||
depositAmounts = mintDepositAmounts(lpTokenAmount);
|
||||
|
||||
// Transfer in all token amounts
|
||||
for (uint i = 0; i < n; ) {
|
||||
if (depositAmounts[i] > 0) {
|
||||
_safeTransferFrom(tokens[i], 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]);
|
||||
|
||||
// For initial deposit, record the actual deposited amounts
|
||||
if (isInitialDeposit) {
|
||||
depositAmounts[i] = bal;
|
||||
}
|
||||
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// If first time, call init, otherwise update proportional change.
|
||||
if (isInitialDeposit) {
|
||||
// Initialize the stabilized LMSR state
|
||||
lmsr.init(newQInternal, tradeFrac, targetSlippage);
|
||||
} else {
|
||||
// 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;
|
||||
|
||||
if (isInitialDeposit) {
|
||||
// Initial provisioning: mint newScaled (as LP units)
|
||||
actualLpToMint = newScaled;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// For subsequent mints, ensure the calculated LP amount is not too different from requested
|
||||
if (!isInitialDeposit) {
|
||||
// Allow for some rounding error but ensure we're not far off from requested amount
|
||||
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");
|
||||
}
|
||||
|
||||
require( actualLpToMint > 0, "mint: zero LP amount");
|
||||
_mint(receiver, actualLpToMint);
|
||||
emit Mint(payer, receiver, depositAmounts, actualLpToMint);
|
||||
}
|
||||
|
||||
/// @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.
|
||||
@@ -324,7 +338,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
// Transfer underlying tokens out to receiver according to computed proportions
|
||||
for (uint i = 0; i < n; ) {
|
||||
if (withdrawAmounts[i] > 0) {
|
||||
_safeTransfer(tokens[i], receiver, withdrawAmounts[i]);
|
||||
tokens[i].safeTransfer(receiver, withdrawAmounts[i]);
|
||||
}
|
||||
unchecked { i++; }
|
||||
}
|
||||
@@ -369,6 +383,139 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
Swaps
|
||||
---------------------- */
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
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 swapToLimitAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
|
||||
|
||||
/// @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.
|
||||
/// @param payer address of the account that pays for the swap
|
||||
/// @param receiver address that will receive the output tokens
|
||||
/// @param inputTokenIndex index of input asset
|
||||
/// @param outputTokenIndex index of output asset
|
||||
/// @param maxAmountIn maximum amount of token i (uint256) to transfer in (inclusive of fees)
|
||||
/// @param limitPrice maximum acceptable marginal price (64.64 fixed point). Pass 0 to ignore.
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
/// @return amountIn actual input used (uint256), amountOut actual output sent (uint256), fee fee taken from the input (uint256)
|
||||
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");
|
||||
|
||||
// Update cached uint balances for i and j using actual balances
|
||||
cachedUintBalances[inputTokenIndex] = balIAfter;
|
||||
cachedUintBalances[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 Swap up to the price limit; computes max input to reach limit then performs swap.
|
||||
/// @dev If balances prevent fully reaching the limit, the function caps and returns actuals.
|
||||
/// The payer must transfer the exact gross input computed by the view.
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline
|
||||
) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
|
||||
uint256 n = tokens.length;
|
||||
require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
|
||||
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: 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 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
|
||||
_quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
|
||||
// Transfer the exact amount needed 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, "swapToLimit: 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, "swapToLimit: non-standard tokenOut");
|
||||
|
||||
// Update caches to actual balances
|
||||
cachedUintBalances[inputTokenIndex] = balIAfter;
|
||||
cachedUintBalances[outputTokenIndex] = balJAfter;
|
||||
|
||||
// Apply swap to LMSR state with the internal amounts
|
||||
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalMax, amountOutInternal);
|
||||
|
||||
// Maintain original event semantics (logs input without fee)
|
||||
emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], amountInUsedUint, amountOutUint);
|
||||
|
||||
return (amountInUsedUint, amountOutUint, feeUint);
|
||||
}
|
||||
|
||||
/// @notice Ceiling fee helper: computes ceil(x * feePpm / 1_000_000)
|
||||
/// @dev Internal helper; public-facing functions use this to ensure fees round up in favor of pool.
|
||||
function _ceilFee(uint256 x, uint256 feePpm) internal pure returns (uint256) {
|
||||
if (feePpm == 0) return 0;
|
||||
// ceil division: (num + denom - 1) / denom
|
||||
return (x * feePpm + 1_000_000 - 1) / 1_000_000;
|
||||
}
|
||||
|
||||
/// @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),
|
||||
@@ -474,139 +621,6 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
require(amountOutUint > 0, "swapToLimit: output zero");
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
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 swapToLimitAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
|
||||
|
||||
/// @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.
|
||||
/// @param payer address of the account that pays for the swap
|
||||
/// @param receiver address that will receive the output tokens
|
||||
/// @param inputTokenIndex index of input asset
|
||||
/// @param outputTokenIndex index of output asset
|
||||
/// @param maxAmountIn maximum amount of token i (uint256) to transfer in (inclusive of fees)
|
||||
/// @param limitPrice maximum acceptable marginal price (64.64 fixed point). Pass 0 to ignore.
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
/// @return amountIn actual input used (uint256), amountOut actual output sent (uint256), fee fee taken from the input (uint256)
|
||||
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)
|
||||
_safeTransferFrom(tokens[inputTokenIndex], 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
|
||||
_safeTransfer(tokens[outputTokenIndex], receiver, amountOutUint);
|
||||
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;
|
||||
|
||||
// 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 Swap up to the price limit; computes max input to reach limit then performs swap.
|
||||
/// @dev If balances prevent fully reaching the limit, the function caps and returns actuals.
|
||||
/// The payer must transfer the exact gross input computed by the view.
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline
|
||||
) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
|
||||
uint256 n = tokens.length;
|
||||
require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
|
||||
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: 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 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
|
||||
_quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
|
||||
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
|
||||
_safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransferAmount);
|
||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
|
||||
|
||||
// Transfer output to receiver and verify exact decrease
|
||||
_safeTransfer(tokens[outputTokenIndex], receiver, amountOutUint);
|
||||
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;
|
||||
|
||||
// Apply swap to LMSR state with the internal amounts
|
||||
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalMax, amountOutInternal);
|
||||
|
||||
// Maintain original event semantics (logs input without fee)
|
||||
emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], amountInUsedUint, amountOutUint);
|
||||
|
||||
return (amountInUsedUint, amountOutUint, feeUint);
|
||||
}
|
||||
|
||||
/// @notice Ceiling fee helper: computes ceil(x * feePpm / 1_000_000)
|
||||
/// @dev Internal helper; public-facing functions use this to ensure fees round up in favor of pool.
|
||||
function _ceilFee(uint256 x, uint256 feePpm) internal pure returns (uint256) {
|
||||
if (feePpm == 0) return 0;
|
||||
// ceil division: (num + denom - 1) / denom
|
||||
return (x * feePpm + 1_000_000 - 1) / 1_000_000;
|
||||
}
|
||||
|
||||
/// @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) {
|
||||
@@ -673,7 +687,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
|
||||
// Record pre-balance and transfer tokens from payer, require exact receipt (revert on fee-on-transfer)
|
||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
_safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransfer);
|
||||
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransfer);
|
||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransfer, "swapMint: non-standard tokenIn");
|
||||
|
||||
@@ -765,7 +779,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
require(amountOutUint > 0, "burnSwap: output zero");
|
||||
|
||||
// Transfer the payout to receiver
|
||||
_safeTransfer(tokens[inputTokenIndex], receiver, amountOutUint);
|
||||
tokens[inputTokenIndex].safeTransfer(receiver, amountOutUint);
|
||||
|
||||
// Burn LP tokens from payer (authorization via allowance)
|
||||
if (msg.sender != payer) {
|
||||
@@ -853,7 +867,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
initialBalances[i] = IERC20(tokens[i]).balanceOf(address(this));
|
||||
|
||||
// Transfer token to recipient
|
||||
_safeTransfer(tokens[i], recipient, amount);
|
||||
tokens[i].safeTransfer(recipient, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,18 +927,6 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
return floorValue;
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
ERC20 helpers (minimal)
|
||||
---------------------- */
|
||||
|
||||
function _safeTransferFrom(address token, address from, address to, uint256 amt) internal {
|
||||
IERC20(token).safeTransferFrom(from, to, amt);
|
||||
}
|
||||
|
||||
function _safeTransfer(address token, address to, uint256 amt) internal {
|
||||
IERC20(token).safeTransfer(to, amt);
|
||||
}
|
||||
|
||||
/// @notice Helper to compute size metric (sum of all asset quantities) from internal balances
|
||||
/// @dev Returns the sum of all provided qInternal_ entries as a Q64.64 value.
|
||||
function _computeSizeMetric(int128[] memory qInternal_) private pure returns (int128) {
|
||||
|
||||
Reference in New Issue
Block a user