gas test
This commit is contained in:
44
doc/introduction.md
Normal file
44
doc/introduction.md
Normal 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
41
research/gas.py
Normal 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()
|
||||
@@ -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
400
test/GasTest.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user