This commit is contained in:
tim
2025-09-18 19:23:31 -04:00
parent 19f14c6a95
commit bd210b56ff
5 changed files with 491 additions and 220 deletions

44
doc/introduction.md Normal file
View File

@@ -0,0 +1,44 @@
# Introduction
[Liquidity Party](https://liquidity.party) is a new game-theoretic multi-asset AMM based on this paper:
[Logarithmic Market Scoring Rules for Modular Combinatorial Information Aggregation](https://mason.gmu.edu/~rhanson/mktscore.pdf) (R. Hanson, 2002)
A Logarithmic Market Scoring Rule (LMSR) is a pricing formula for AMM's that know only their current asset inventories and no other information, naturally supporting multi-asset
Compared to Constant Product (CP) markets, LMSR offers:
1. Less slippage than CP for small and medium trade sizes
2. N-asset pools for trading long-tail pairs in a single hop
3. Lower fees (smaller spread)
## Deeper Liquidity
According to game theory, CP's slope at the current marginal price is too steep, overcharging takers with too much slippage at small and medium trade sizes. LMSR pools offer less slippage and cheaper liquidity for the small and medium trade sizes common for arbitrageurs and aggregators.
## Multi-asset
Naturally multi-asset, Liquidity Party altcoin pools provide direct, one-hop swaps on otherwise illiquid multi-hop pairs. Pools will quote any pair combination available in the pool:
| Assets | Pairs | Swap Gas | Mint Gas |
|-------:|------:|---------:|----------:|
| 2 | 1 | 146,000 | 149,000 |
| 10 | 45 | 157,000 | 426,000 |
| 20 | 190 | 171,000 | 772,000 |
| 50 | 1225 | 213,000 | 1,810,000 |
| 100 | 4950 | 283,000 | 3,542,000 |
Liquidity Party aggregates scarce, low market cap assets into a single pool, providing one-hop liquidity for exotic pairs without fragmenting LP assets. CP pools would need 190x the LP assets to provide the same pairwise liquidity as a single 20-asset Liquidity Party pool, due to asset fragmentation.
## Lower Fees
Since market makers offer the option to take either side of the market, they must receive a subsidy or charge a fee (spread) to compensate for adverse selection (impermanent loss). By protecting LP's against common value-extraction scenarios, LMSR pools have a reduced risk premium resulting in lower fees for takers.
### Minimized Impermanent Loss
### No Intra-Pool Arbitrage
LMSR
This leads to LP's of CP pools demanding higher fees than theoretically necessary. By minimizing impermanent loss for LP's, LMSR pools reduce the risk premium, providing lower fees and higher liquidity for takers.

41
research/gas.py Normal file
View File

@@ -0,0 +1,41 @@
import logging
import pandas as pd
import matplotlib.pyplot as plt
log = logging.getLogger(__name__)
# Initialize a DataFrame with max gas values for each function across different pool sizes
# Row index: number of assets in the pool
# Columns: function names
# Cell values: max gas values
gas_data = {
'allTokens': [7489, 25754, 48587, 117095, 231306],
'balanceOf': [2806, 2806, 2806, 2806, 2806],
'burn': [121374, 388107, 721563, 1722086, 3390257],
'burnSwap': [129791, 410564, 758861, 1799954, 3560756],
'mint': [149100, 425896, 771933, 1810329, 3541934],
'swap': [145931, 156792, 171186, 213066, 283078],
'swapMint': [429036, 2612211, 5339543, 13644860, 27527806]
}
# Create DataFrame with row indices as the number of assets in the pool
gas_df = pd.DataFrame(gas_data, index=[2, 10, 20, 50, 100])
gas_df.index.name = 'Assets in Pool'
# Print the DataFrame
# gas_df['swap'].plot(xticks=gas_df.index)
# plt.ylim(140_000, 300_000)
# plt.title('Swap Gas Cost')
# plt.show()
#
# gas_df[['mint', 'burn']].plot(xticks=gas_df.index)
# plt.ylim(40_000, 4_000_000)
# plt.title('Mint/Burn Gas Cost')
# plt.show()
gas_df[['swapMint', 'burnSwap']].div(1_000_000).plot(xticks=gas_df.index)
plt.title('SwapMint/BurnSwap Gas Cost')
plt.ylim(0, 30)
plt.ylabel('Gas Cost (millions)')
plt.show()

View File

@@ -44,7 +44,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
//
// Internal state
//
LMSRStabilized.State internal lmsr;
// Cached on-chain balances (uint) and internal 64.64 representation
@@ -52,9 +52,9 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256[] internal cachedUintBalances;
uint256[] internal bases; // per-token uint base used to scale token amounts <-> internal
function denominators() external view returns (uint256[] memory) { return bases; }
mapping(address=>uint) public tokenAddressToIndexPlusOne; // Uses index+1 so a result of 0 indicates a failed lookup
uint256 public constant LP_SCALE = 1e18; // Scale used to convert LMSR lastTotal (Q64.64) into LP token units (uint)
/// @param name_ LP token name
@@ -766,8 +766,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
}
}
}
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
/// @dev The caller of this method receives a callback in the form of IPartyFlashCallback#partyFlashCallback
/// @param recipient The address which will receive the token amounts
@@ -831,7 +831,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
}
}
/* ----------------------
Conversion helpers
---------------------- */

400
test/GasTest.sol Normal file
View File

@@ -0,0 +1,400 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30;
import "forge-std/Test.sol";
import "@abdk/ABDKMath64x64.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../src/PartyPool.sol";
// Import the flash callback interface
import "../src/IPartyFlashCallback.sol";
/// @notice Test contract that implements the flash callback for testing flash loans
contract FlashBorrower is IPartyFlashCallback {
enum Action {
NORMAL, // Normal repayment
REPAY_NONE, // Don't repay anything
REPAY_PARTIAL, // Repay less than required
REPAY_NO_FEE, // Repay only the principal without fee
REPAY_EXACT, // Repay exactly the required amount
REPAY_EXTRA // Repay more than required (donation)
}
Action public action;
address public pool;
address public recipient;
address[] public tokens;
constructor(address _pool, address[] memory _tokens) {
pool = _pool;
tokens = _tokens;
}
function setAction(Action _action, address _recipient) external {
action = _action;
recipient = _recipient;
}
function flash(uint256[] memory amounts) external {
PartyPool(pool).flash(recipient, amounts, "");
}
function partyFlashCallback(
uint256[] memory loanAmounts,
uint256[] memory repaymentAmounts,
bytes calldata /* data */
) external override {
require(msg.sender == pool, "Callback not called by pool");
if (action == Action.NORMAL || action == Action.REPAY_EXTRA) {
// Normal or extra repayment - transfer required amounts back to pool
for (uint256 i = 0; i < loanAmounts.length; i++) {
if (loanAmounts[i] > 0) {
uint256 repaymentAmount = repaymentAmounts[i];
// For REPAY_EXTRA, add 1 to each repayment
if (action == Action.REPAY_EXTRA) {
repaymentAmount += 1;
}
// Transfer from recipient back to pool
TestERC20(tokens[i]).transferFrom(
recipient,
pool,
repaymentAmount
);
}
}
} else if (action == Action.REPAY_PARTIAL) {
// Repay half of the required amounts
for (uint256 i = 0; i < loanAmounts.length; i++) {
if (loanAmounts[i] > 0) {
uint256 partialRepayment = repaymentAmounts[i] / 2;
TestERC20(tokens[i]).transferFrom(
recipient,
pool,
partialRepayment
);
}
}
} else if (action == Action.REPAY_NO_FEE) {
// Repay only the principal without fee
for (uint256 i = 0; i < loanAmounts.length; i++) {
if (loanAmounts[i] > 0) {
TestERC20(tokens[i]).transferFrom(
recipient,
pool,
loanAmounts[i]
);
}
}
} else if (action == Action.REPAY_EXACT) {
// Repay exactly what was required
for (uint256 i = 0; i < loanAmounts.length; i++) {
if (loanAmounts[i] > 0) {
TestERC20(tokens[i]).transferFrom(
recipient,
pool,
repaymentAmounts[i]
);
}
}
}
// For REPAY_NONE, do nothing (don't repay)
}
}
/// @notice Minimal ERC20 token for tests with an external mint function.
contract TestERC20 is ERC20 {
constructor(string memory name_, string memory symbol_, uint256 initialSupply) ERC20(name_, symbol_) {
if (initialSupply > 0) {
_mint(msg.sender, initialSupply);
}
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
// Expose convenient approve helper for tests (not necessary but handy)
function approveMax(address spender) external {
_approve(msg.sender, spender, type(uint256).max);
}
}
/// @notice Gas testing contract for PartyPool - contains all gas measurement tests
contract GasTest is Test {
using ABDKMath64x64 for int128;
PartyPool pool2;
PartyPool pool10;
PartyPool pool20;
PartyPool pool50;
address alice;
address bob;
// Common parameters
int128 tradeFrac;
int128 targetSlippage;
uint256 constant INIT_BAL = 1_000_000; // initial token units for each token (internal==amount when base==1)
uint256 constant BASE = 1; // use base=1 so internal amounts correspond to raw integers (Q64.64 units)
/// @notice Helper function to create a pool with the specified number of tokens
function createPool(uint256 numTokens) internal returns (PartyPool) {
// Deploy tokens dynamically
address[] memory tokens = new address[](numTokens);
uint256[] memory bases = new uint256[](numTokens);
for (uint256 i = 0; i < numTokens; i++) {
string memory name = string(abi.encodePacked("T", vm.toString(i)));
TestERC20 token = new TestERC20(name, name, 0);
tokens[i] = address(token);
bases[i] = BASE;
// Mint initial balances for pool initialization and test users
token.mint(address(this), INIT_BAL);
token.mint(alice, INIT_BAL);
token.mint(bob, INIT_BAL);
}
// Deploy pool with a small fee to test fee-handling paths (use 1000 ppm = 0.1%)
uint256 feePpm = 1000;
string memory poolName = string(abi.encodePacked("LP", vm.toString(numTokens)));
PartyPool newPool = new PartyPool(poolName, poolName, tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm);
// Transfer initial deposit amounts into pool before initial mint
for (uint256 i = 0; i < numTokens; i++) {
TestERC20(tokens[i]).transfer(address(newPool), INIT_BAL);
}
// Perform initial mint (initial deposit); receiver is this contract
newPool.mint(address(0), address(this), 0, 0);
return newPool;
}
function setUp() public {
alice = address(0xA11ce);
bob = address(0xB0b);
// Configure LMSR parameters similar to other tests: trade size 1% of asset -> 0.01, slippage 0.001
tradeFrac = ABDKMath64x64.divu(100, 10_000); // 0.01
targetSlippage = ABDKMath64x64.divu(10, 10_000); // 0.001
// Create pools of different sizes
pool2 = createPool(2);
pool10 = createPool(10);
pool20 = createPool(20);
pool50 = createPool(50);
}
/// @notice Setup a flash borrower for testing
function setupFlashBorrower() internal returns (FlashBorrower borrower) {
// Get token addresses from the 2-token pool
address[] memory tokenAddresses = pool2.allTokens();
// Deploy the borrower contract
borrower = new FlashBorrower(address(pool2), tokenAddresses);
// Mint tokens to alice to be used for repayments and approve borrower
vm.startPrank(alice);
for (uint256 i = 0; i < tokenAddresses.length; i++) {
TestERC20(tokenAddresses[i]).mint(alice, INIT_BAL * 2);
TestERC20(tokenAddresses[i]).approve(address(borrower), type(uint256).max);
}
vm.stopPrank();
}
/// @notice Helper function: perform 10 swaps back-and-forth between the first two tokens.
function _performSwapGasTest(PartyPool testPool) internal {
address[] memory tokens = testPool.allTokens();
require(tokens.length >= 2, "Pool must have at least 2 tokens");
// Ensure alice approves pool for both tokens
vm.prank(alice);
TestERC20(tokens[0]).approve(address(testPool), type(uint256).max);
vm.prank(alice);
TestERC20(tokens[1]).approve(address(testPool), type(uint256).max);
uint256 maxIn = 1_000;
// Perform 10 swaps alternating directions to avoid large imbalance
for (uint256 i = 0; i < 10; i++) {
vm.prank(alice);
if (i % 2 == 0) {
// swap token0 -> token1
testPool.swap(alice, alice, 0, 1, maxIn, 0, 0);
} else {
// swap token1 -> token0
testPool.swap(alice, alice, 1, 0, maxIn, 0, 0);
}
}
}
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two tokens in the 2-token pool.
function testSwapGasPair() public {
_performSwapGasTest(pool2);
}
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two tokens in the 10-token pool.
function testSwapGasTen() public {
_performSwapGasTest(pool10);
}
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two tokens in the 20-token pool.
function testSwapGasTwenty() public {
_performSwapGasTest(pool20);
}
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two tokens in the 100-token pool.
function testSwapGasFifty() public {
_performSwapGasTest(pool50);
}
/// @notice Helper function: alternate swapMint then burnSwap to keep pool size roughly stable.
function _performSwapMintBurnSwapGasTest(PartyPool testPool) internal {
uint256 iterations = 10;
uint256 input = 1_000;
address[] memory tokens = testPool.allTokens();
// Top up alice so repeated operations won't fail
TestERC20(tokens[0]).mint(alice, iterations * input * 2);
vm.startPrank(alice);
TestERC20(tokens[0]).approve(address(testPool), type(uint256).max);
for (uint256 k = 0; k < iterations; k++) {
// Mint LP by providing single-token input; receive LP minted
uint256 minted = testPool.swapMint(alice, alice, 0, input, 0);
// If nothing minted (numerical edge), skip burn step
if (minted == 0) continue;
// Immediately burn the minted LP back to tokens, targeting the same token index
testPool.burnSwap(alice, alice, minted, 0, 0);
}
vm.stopPrank();
}
/// @notice Gas-style test: alternate swapMint then burnSwap on the 2-token pool to keep pool size roughly stable.
function testSwapMintBurnSwapGasPair() public {
_performSwapMintBurnSwapGasTest(pool2);
}
/// @notice Gas-style test: alternate swapMint then burnSwap on the 10-token pool to keep pool size roughly stable.
function testSwapMintBurnSwapGasTen() public {
_performSwapMintBurnSwapGasTest(pool10);
}
/// @notice Gas-style test: alternate swapMint then burnSwap on the 20-token pool to keep pool size roughly stable.
function testSwapMintBurnSwapGasTwenty() public {
_performSwapMintBurnSwapGasTest(pool20);
}
/// @notice Gas-style test: alternate swapMint then burnSwap on the 100-token pool to keep pool size roughly stable.
function testSwapMintBurnSwapGasFifty() public {
_performSwapMintBurnSwapGasTest(pool50);
}
/// @notice Helper function: combined gas test (mint then burn) using mint() and burn().
/// Alternates minting a tiny LP amount and immediately burning the actual minted LP back to avoid net pool depletion.
function _performMintBurnGasTest(PartyPool testPool) internal {
uint256 iterations = 50;
uint256 input = 1_000;
address[] memory poolTokens = testPool.allTokens();
vm.startPrank(alice);
// Mint additional tokens to alice and approve pool to transfer tokens for proportional mint
for (uint256 i = 0; i < poolTokens.length; i++) {
TestERC20(poolTokens[i]).mint(alice, iterations * input * 2);
TestERC20(poolTokens[i]).approve(address(testPool), type(uint256).max);
}
for (uint256 k = 0; k < iterations; k++) {
// Request a tiny LP mint (1 wei) - pool will compute deposits and transfer from alice
uint256 lpRequest = 1;
// Snapshot alice LP before to compute actual minted
uint256 lpBefore = testPool.balanceOf(alice);
// Perform mint; this will transfer underlying from alice into pool
testPool.mint(alice, alice, lpRequest, 0);
uint256 lpAfter = testPool.balanceOf(alice);
uint256 actualMinted = lpAfter - lpBefore;
// If nothing minted due to rounding edge, skip burn
if (actualMinted == 0) {
continue;
}
// Burn via plain burn() which will transfer underlying back to alice and burn LP
testPool.burn(alice, alice, actualMinted, 0);
}
vm.stopPrank();
}
/// @notice Combined gas test (mint then burn) on 2-token pool using mint() and burn().
/// Alternates minting a tiny LP amount and immediately burning the actual minted LP back to avoid net pool depletion.
function testMintBurnGasPair() public {
_performMintBurnGasTest(pool2);
}
/// @notice Combined gas test (mint then burn) on 10-token pool using mint() and burn().
/// Alternates small mints and burns to keep the pool size roughly stable.
function testMintBurnGasTen() public {
_performMintBurnGasTest(pool10);
}
/// @notice Combined gas test (mint then burn) on 20-token pool using mint() and burn().
/// Alternates small mints and burns to keep the pool size roughly stable.
function testMintBurnGasTwenty() public {
_performMintBurnGasTest(pool20);
}
/// @notice Combined gas test (mint then burn) on 100-token pool using mint() and burn().
/// Alternates small mints and burns to keep the pool size roughly stable.
function testMintBurnGasFifty() public {
_performMintBurnGasTest(pool50);
}
/// @notice Gas measurement: flash with single token
function testFlashGasSingleToken() public {
FlashBorrower borrower = setupFlashBorrower();
// Configure borrower
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
// Create loan request for single token (get array size from pool)
address[] memory poolTokens = pool2.allTokens();
uint256[] memory amounts = new uint256[](poolTokens.length);
amounts[0] = 1000;
// Execute flash loan 10 times to measure gas
for (uint256 i = 0; i < 10; i++) {
borrower.flash(amounts);
}
}
/// @notice Gas measurement: flash with multiple tokens
function testFlashGasMultipleTokens() public {
FlashBorrower borrower = setupFlashBorrower();
// Configure borrower
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
// Create loan request for multiple tokens (get array size from pool)
address[] memory poolTokens = pool2.allTokens();
uint256[] memory amounts = new uint256[](poolTokens.length);
amounts[0] = 1000;
amounts[1] = 2000;
// Execute flash loan 10 times to measure gas
for (uint256 i = 0; i < 10; i++) {
borrower.flash(amounts);
}
}
}

View File

@@ -505,136 +505,6 @@ contract PartyPoolTest is Test {
assertEq(token1.balanceOf(bob) >= amountOut, true);
}
/// @notice Gas measurement: perform 100 swaps back-and-forth between token0 and token1.
function testSwapGas3() public {
// Ensure alice approves pool for both tokens
vm.prank(alice);
token0.approve(address(pool), type(uint256).max);
vm.prank(alice);
token1.approve(address(pool), type(uint256).max);
uint256 maxIn = 1_000;
// Perform 100 swaps alternating directions to avoid large imbalance
for (uint256 i = 0; i < 100; i++) {
vm.prank(alice);
if (i % 2 == 0) {
// swap token0 -> token1
pool.swap(alice, alice, 0, 1, maxIn, 0, 0);
} else {
// swap token1 -> token0
pool.swap(alice, alice, 1, 0, maxIn, 0, 0);
}
}
}
/// @notice Gas measurement: perform 100 swaps back-and-forth between token0 and token1 in the 10-token pool.
function testSwapGas10() public {
// Ensure alice approves pool10 for both tokens
vm.prank(alice);
token0.approve(address(pool10), type(uint256).max);
vm.prank(alice);
token1.approve(address(pool10), type(uint256).max);
uint256 maxIn = 1_000;
// Perform 100 swaps alternating directions to avoid large imbalance
for (uint256 i = 0; i < 100; i++) {
vm.prank(alice);
if (i % 2 == 0) {
// swap token0 -> token1
pool10.swap(alice, alice, 0, 1, maxIn, 0, 0);
} else {
// swap token1 -> token0
pool10.swap(alice, alice, 1, 0, maxIn, 0, 0);
}
}
}
/// @notice Gas-style test: alternate swapMint then burnSwap on the 3-token pool to keep pool size roughly stable.
function testSwapMintBurnSwapGas3() public {
uint256 iterations = 100;
uint256 input = 1_000;
// Top up alice so repeated operations won't fail
token0.mint(alice, iterations * input * 2);
vm.startPrank(alice);
token0.approve(address(pool), type(uint256).max);
for (uint256 k = 0; k < iterations; k++) {
// Mint LP by providing single-token input; receive LP minted
uint256 minted = pool.swapMint(alice, alice, 0, input, 0);
// If nothing minted (numerical edge), skip burn step
if (minted == 0) continue;
// Immediately burn the minted LP back to tokens, targeting the same token index
pool.burnSwap(alice, alice, minted, 0, 0);
}
vm.stopPrank();
}
/// @notice Gas-style test: alternate swapMint then burnSwap on the 10-token pool to keep pool size roughly stable.
function testSwapMintBurnSwapGas10() public {
uint256 iterations = 100;
uint256 input = 1_000;
// Top up alice so repeated operations won't fail
token0.mint(alice, iterations * input * 2);
vm.startPrank(alice);
token0.approve(address(pool10), type(uint256).max);
for (uint256 k = 0; k < iterations; k++) {
uint256 minted = pool10.swapMint(alice, alice, 0, input, 0);
if (minted == 0) continue;
pool10.burnSwap(alice, alice, minted, 0, 0);
}
vm.stopPrank();
}
/// @notice Combined gas test (mint then burn) on 3-token pool using mint() and burn().
/// Alternates minting a tiny LP amount and immediately burning the actual minted LP back to avoid net pool depletion.
function testMintBurnGas3() public {
uint256 iterations = 50;
uint256 input = 1_000;
// Ensure alice has enough tokens for all mints
token0.mint(alice, iterations * input * 2);
token1.mint(alice, iterations * input * 2);
token2.mint(alice, iterations * input * 2);
vm.startPrank(alice);
// Approve pool to transfer tokens for proportional mint
token0.approve(address(pool), type(uint256).max);
token1.approve(address(pool), type(uint256).max);
token2.approve(address(pool), type(uint256).max);
for (uint256 k = 0; k < iterations; k++) {
// Request a tiny LP mint (1 wei) - pool will compute deposits and transfer from alice
uint256 lpRequest = 1;
// Snapshot alice LP before to compute actual minted
uint256 lpBefore = pool.balanceOf(alice);
// Perform mint; this will transfer underlying from alice into pool
pool.mint(alice, alice, lpRequest, 0);
uint256 lpAfter = pool.balanceOf(alice);
uint256 actualMinted = lpAfter - lpBefore;
// If nothing minted due to rounding edge, skip burn
if (actualMinted == 0) {
continue;
}
// Burn via plain burn() which will transfer underlying back to alice and burn LP
pool.burn(alice, alice, actualMinted, 0);
}
vm.stopPrank();
}
/// @notice Verify mintDepositAmounts matches the actual token transfers performed by mint()
function testMintDepositAmountsMatchesMint_3TokenPool() public {
@@ -869,55 +739,6 @@ contract PartyPoolTest is Test {
}
}
/// @notice Combined gas test (mint then burn) on 10-token pool using mint() and burn().
/// Alternates small mints and burns to keep the pool size roughly stable.
function testMintBurnGas10() public {
uint256 iterations = 50;
uint256 input = 1_000;
// Ensure alice has enough tokens for all mints across 10 tokens
for (uint i = 0; i < 10; i++) {
// mint to alice corresponding token; use token0..token9 mapping in setUp ordering
if (i == 0) token0.mint(alice, iterations * input * 2);
else if (i == 1) token1.mint(alice, iterations * input * 2);
else if (i == 2) token2.mint(alice, iterations * input * 2);
else if (i == 3) token3.mint(alice, iterations * input * 2);
else if (i == 4) token4.mint(alice, iterations * input * 2);
else if (i == 5) token5.mint(alice, iterations * input * 2);
else if (i == 6) token6.mint(alice, iterations * input * 2);
else if (i == 7) token7.mint(alice, iterations * input * 2);
else if (i == 8) token8.mint(alice, iterations * input * 2);
else if (i == 9) token9.mint(alice, iterations * input * 2);
}
vm.startPrank(alice);
// Approve pool10 to transfer tokens for proportional mint
token0.approve(address(pool10), type(uint256).max);
token1.approve(address(pool10), type(uint256).max);
token2.approve(address(pool10), type(uint256).max);
token3.approve(address(pool10), type(uint256).max);
token4.approve(address(pool10), type(uint256).max);
token5.approve(address(pool10), type(uint256).max);
token6.approve(address(pool10), type(uint256).max);
token7.approve(address(pool10), type(uint256).max);
token8.approve(address(pool10), type(uint256).max);
token9.approve(address(pool10), type(uint256).max);
for (uint256 k = 0; k < iterations; k++) {
uint256 lpRequest = 1;
uint256 lpBefore = pool10.balanceOf(alice);
pool10.mint(alice, alice, lpRequest, 0);
uint256 lpAfter = pool10.balanceOf(alice);
uint256 actualMinted = lpAfter - lpBefore;
if (actualMinted == 0) continue;
pool10.burn(alice, alice, actualMinted, 0);
}
vm.stopPrank();
}
/// @notice Basic test for swapMint: single-token deposit -> LP minted
function testSwapMintBasic() public {
@@ -1389,39 +1210,4 @@ contract PartyPoolTest is Test {
pool.flash(alice, wrongLengthAmounts, "");
}
/// @notice Gas measurement: flash with single token
function testFlashGasSingleToken() public {
FlashBorrower borrower = setupFlashBorrower();
// Configure borrower
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
// Create loan request for single token
uint256[] memory amounts = new uint256[](3);
amounts[0] = 1000;
// Execute flash loan 10 times to measure gas
for (uint256 i = 0; i < 10; i++) {
borrower.flash(amounts);
}
}
/// @notice Gas measurement: flash with multiple tokens
function testFlashGasMultipleTokens() public {
FlashBorrower borrower = setupFlashBorrower();
// Configure borrower
borrower.setAction(FlashBorrower.Action.NORMAL, alice);
// Create loan request for multiple tokens
uint256[] memory amounts = new uint256[](3);
amounts[0] = 1000;
amounts[1] = 2000;
amounts[2] = 3000;
// Execute flash loan 10 times to measure gas
for (uint256 i = 0; i < 10; i++) {
borrower.flash(amounts);
}
}
}