CREATE2 callback validation; init code storage contracts
This commit is contained in:
146
test/Deploy.sol
146
test/Deploy.sol
@@ -2,59 +2,61 @@
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {IPartyInfo} from "../src/IPartyInfo.sol";
|
||||
import {IPartyPlanner} from "../src/IPartyPlanner.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {NativeWrapper} from "../src/NativeWrapper.sol";
|
||||
import {PartyInfo} from "../src/PartyInfo.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {PartyPool} from "../src/PartyPool.sol";
|
||||
import {PartyPoolBalancedPair} from "../src/PartyPoolBalancedPair.sol";
|
||||
import {PartyPoolDeployer, PartyPoolBalancedPairDeployer} from "../src/PartyPoolDeployer.sol";
|
||||
import {PartyPoolInitCode, PartyPoolBalancedPairInitCode} from "../src/PartyPoolDeployer.sol";
|
||||
import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol";
|
||||
import {PartyInfo} from "../src/PartyInfo.sol";
|
||||
import {WETH9} from "./WETH9.sol";
|
||||
import {MockERC20} from "./MockERC20.sol";
|
||||
|
||||
library Deploy {
|
||||
address internal constant PROTOCOL_FEE_RECEIVER = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; // dev account #1
|
||||
uint256 internal constant PROTOCOL_FEE_PPM = 100_000; // 10%
|
||||
|
||||
function newPartyPlanner() internal returns (PartyPlanner) {
|
||||
function newPartyPlanner() internal returns (IPartyPlanner) {
|
||||
NativeWrapper wrapper = new WETH9();
|
||||
return newPartyPlanner(msg.sender, wrapper);
|
||||
return newPartyPlanner(address(this), wrapper);
|
||||
}
|
||||
|
||||
function newPartyPlanner(address owner) internal returns (PartyPlanner) {
|
||||
function newPartyPlanner(address owner) internal returns (IPartyPlanner) {
|
||||
NativeWrapper wrapper = new WETH9();
|
||||
return newPartyPlanner(owner, wrapper);
|
||||
}
|
||||
|
||||
function newPartyPlanner(address owner, NativeWrapper wrapper) internal returns (PartyPlanner) {
|
||||
function newPartyPlanner(address owner, NativeWrapper wrapper) internal returns (IPartyPlanner) {
|
||||
return new PartyPlanner(
|
||||
owner,
|
||||
wrapper,
|
||||
new PartyPoolSwapImpl(wrapper),
|
||||
new PartyPoolMintImpl(wrapper),
|
||||
new PartyPoolDeployer(),
|
||||
new PartyPoolBalancedPairDeployer(),
|
||||
new PartyPoolInitCode(),
|
||||
new PartyPoolBalancedPairInitCode(),
|
||||
PROTOCOL_FEE_PPM,
|
||||
PROTOCOL_FEE_RECEIVER
|
||||
);
|
||||
}
|
||||
|
||||
function newPartyPool(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 _kappa,
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
bool _stable
|
||||
) internal returns (PartyPool) {
|
||||
bool _stable,
|
||||
uint256 _initialBalance,
|
||||
uint256 _lpTokens
|
||||
) internal returns (IPartyPool pool) {
|
||||
NativeWrapper wrapper = new WETH9();
|
||||
return newPartyPool(owner_, name_, symbol_, tokens_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable);
|
||||
(pool,) = newPartyPool2(NPPArgs(name_, symbol_, tokens_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable, _initialBalance, _lpTokens));
|
||||
}
|
||||
|
||||
function newPartyPool(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
@@ -62,44 +64,92 @@ library Deploy {
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
NativeWrapper wrapper,
|
||||
bool _stable
|
||||
) internal returns (PartyPool) {
|
||||
bool _stable,
|
||||
uint256 _initialBalance,
|
||||
uint256 _lpTokens
|
||||
) internal returns (IPartyPool pool) {
|
||||
(pool,) = newPartyPool2(NPPArgs(name_, symbol_, tokens_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable, _initialBalance, _lpTokens));
|
||||
}
|
||||
|
||||
|
||||
function newPartyPool2(
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 _kappa,
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
bool _stable,
|
||||
uint256 _initialBalance,
|
||||
uint256 _lpTokens
|
||||
) internal returns (IPartyPool pool, uint256 lpTokens) {
|
||||
NativeWrapper wrapper = new WETH9();
|
||||
return newPartyPool2(NPPArgs(name_, symbol_, tokens_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable, _initialBalance, _lpTokens));
|
||||
}
|
||||
|
||||
struct NPPVars {
|
||||
address planner;
|
||||
uint256[] feesArr;
|
||||
uint256[] deposits;
|
||||
}
|
||||
|
||||
struct NPPArgs {
|
||||
string name;
|
||||
string symbol;
|
||||
IERC20[] tokens;
|
||||
int128 kappa;
|
||||
uint256 swapFeePpm;
|
||||
uint256 flashFeePpm;
|
||||
NativeWrapper wrapper;
|
||||
bool stable;
|
||||
uint256 initialBalance;
|
||||
uint256 lpTokens;
|
||||
}
|
||||
|
||||
function newPartyPool2( NPPArgs memory args ) internal returns (IPartyPool pool, uint256 lpTokens) {
|
||||
NPPVars memory v = NPPVars(
|
||||
address(newPartyPlanner(address(this), args.wrapper)),
|
||||
new uint256[](args.tokens.length),
|
||||
new uint256[](args.tokens.length)
|
||||
);
|
||||
address self = address(this);
|
||||
|
||||
// Build per-asset fee vector from scalar for tests
|
||||
uint256[] memory feesArr = new uint256[](tokens_.length);
|
||||
for (uint256 i = 0; i < tokens_.length; i++) { feesArr[i] = _swapFeePpm; }
|
||||
return _stable && tokens_.length == 2 ?
|
||||
new PartyPoolBalancedPair(
|
||||
owner_,
|
||||
name_,
|
||||
symbol_,
|
||||
tokens_,
|
||||
_kappa,
|
||||
feesArr,
|
||||
_flashFeePpm,
|
||||
PROTOCOL_FEE_PPM,
|
||||
PROTOCOL_FEE_RECEIVER,
|
||||
wrapper,
|
||||
new PartyPoolSwapImpl(wrapper),
|
||||
new PartyPoolMintImpl(wrapper)
|
||||
) :
|
||||
new PartyPool(
|
||||
owner_,
|
||||
name_,
|
||||
symbol_,
|
||||
tokens_,
|
||||
_kappa,
|
||||
feesArr,
|
||||
_flashFeePpm,
|
||||
PROTOCOL_FEE_PPM,
|
||||
PROTOCOL_FEE_RECEIVER,
|
||||
wrapper,
|
||||
new PartyPoolSwapImpl(wrapper),
|
||||
new PartyPoolMintImpl(wrapper)
|
||||
for (uint256 i = 0; i < args.tokens.length; i++) { v.feesArr[i] = args.swapFeePpm; }
|
||||
|
||||
for (uint256 i = 0; i < args.tokens.length; i++) {
|
||||
if (address(args.tokens[i]) == address(args.wrapper)) {
|
||||
// Not a MockERC20. Wrap coins instead of minting.
|
||||
args.wrapper.deposit{value: args.initialBalance}();
|
||||
args.wrapper.approve(v.planner, args.initialBalance);
|
||||
v.deposits[i] = args.initialBalance;
|
||||
}
|
||||
else {
|
||||
MockERC20 t = MockERC20(address(args.tokens[i]));
|
||||
t.mint(self, args.initialBalance);
|
||||
t.approve(v.planner, args.initialBalance);
|
||||
v.deposits[i] = args.initialBalance;
|
||||
}
|
||||
}
|
||||
|
||||
(pool, lpTokens) = IPartyPlanner(v.planner).newPool(
|
||||
args.name,
|
||||
args.symbol,
|
||||
args.tokens,
|
||||
args.kappa,
|
||||
args.swapFeePpm,
|
||||
args.flashFeePpm,
|
||||
args.stable,
|
||||
self,
|
||||
self,
|
||||
v.deposits,
|
||||
args.lpTokens,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function newInfo() internal returns (PartyInfo) {
|
||||
function newInfo() internal returns (IPartyInfo) {
|
||||
NativeWrapper wrapper = new WETH9();
|
||||
return new PartyInfo(new PartyPoolSwapImpl(wrapper), new PartyPoolMintImpl(wrapper));
|
||||
}
|
||||
|
||||
@@ -4,14 +4,23 @@
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
|
||||
import {CommonBase} from "../lib/forge-std/src/Base.sol";
|
||||
import {StdAssertions} from "../lib/forge-std/src/StdAssertions.sol";
|
||||
import {StdChains} from "../lib/forge-std/src/StdChains.sol";
|
||||
import {StdCheats, StdCheatsSafe} from "../lib/forge-std/src/StdCheats.sol";
|
||||
import {StdUtils} from "../lib/forge-std/src/StdUtils.sol";
|
||||
import {Test} from "../lib/forge-std/src/Test.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {Funding} from "../src/Funding.sol";
|
||||
import {IPartyInfo} from "../src/IPartyInfo.sol";
|
||||
import {IPartyPlanner} from "../src/IPartyPlanner.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {LMSRStabilized} from "../src/LMSRStabilized.sol";
|
||||
import {PartyInfo} from "../src/PartyInfo.sol";
|
||||
import {PartyPool} from "../src/PartyPool.sol";
|
||||
import {PartyPoolDeployer} from "../src/PartyPoolDeployer.sol";
|
||||
import {PartySwapCallbackVerifier} from "../src/PartySwapCallbackVerifier.sol";
|
||||
import {Deploy} from "./Deploy.sol";
|
||||
import {TestERC20, SwapCallbackContract} from "./FundingSwapTest.sol";
|
||||
|
||||
/// @notice Minimal ERC20 token for tests with an external mint function.
|
||||
contract TestERC20 is ERC20 {
|
||||
@@ -37,9 +46,11 @@ contract SwapCallbackContract {
|
||||
address public pool;
|
||||
address public tokenSource;
|
||||
bool public shouldFail;
|
||||
IPartyPlanner public planner;
|
||||
|
||||
constructor(address _pool) {
|
||||
constructor(address _pool, IPartyPlanner _planner) {
|
||||
pool = _pool;
|
||||
planner = _planner;
|
||||
}
|
||||
|
||||
function setTokenSource(address _tokenSource) external {
|
||||
@@ -51,16 +62,17 @@ contract SwapCallbackContract {
|
||||
}
|
||||
|
||||
/// @notice Called by PartyPool.swap on the payer. Signature must be:
|
||||
/// provideFunding(address token, uint256 amount)
|
||||
/// provideFunding(bytes32 nonce, IERC20 inputToken, uint256 amount, bytes memory data)
|
||||
/// @dev The pool will call this function to request the input token; this function
|
||||
/// pulls funds from tokenSource (via ERC20.transferFrom) into the pool.
|
||||
function provideFunding(address token, uint256 amount) external {
|
||||
function provideFunding(bytes32 nonce, IERC20 token, uint256 amount, bytes memory) external {
|
||||
PartySwapCallbackVerifier.verifyCallback(planner, nonce);
|
||||
require(msg.sender == pool, "Callback not called by pool");
|
||||
if (shouldFail) revert("callback failed");
|
||||
require(tokenSource != address(0), "no token source");
|
||||
|
||||
// Pull the required tokens from tokenSource into the pool
|
||||
TestERC20(token).transferFrom(tokenSource, pool, amount);
|
||||
token.transferFrom(tokenSource, pool, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +85,10 @@ contract FundingTest is Test {
|
||||
TestERC20 token0;
|
||||
TestERC20 token1;
|
||||
TestERC20 token2;
|
||||
PartyPool pool;
|
||||
PartyPool poolZeroFee;
|
||||
PartyInfo info;
|
||||
IPartyPlanner planner;
|
||||
IPartyPool pool;
|
||||
IPartyPool poolZeroFee;
|
||||
IPartyInfo info;
|
||||
SwapCallbackContract callbackContract;
|
||||
|
||||
address alice;
|
||||
@@ -117,31 +130,26 @@ contract FundingTest is Test {
|
||||
// Deploy pool with a small fee (0.1%)
|
||||
uint256 feePpm = 1000;
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, kappa, feePpm, feePpm, false);
|
||||
|
||||
// Transfer initial deposit amounts into pool
|
||||
token0.transfer(address(pool), INIT_BAL);
|
||||
token1.transfer(address(pool), INIT_BAL);
|
||||
token2.transfer(address(pool), INIT_BAL);
|
||||
planner = Deploy.newPartyPlanner();
|
||||
uint256[] memory deposits = new uint256[](tokens.length);
|
||||
for(uint256 i=0; i<deposits.length; i++)
|
||||
deposits[i] = INIT_BAL;
|
||||
|
||||
// Perform initial mint
|
||||
pool.initialMint(address(this), INIT_BAL * tokens.length * 10**18);
|
||||
token0.mint(address(this), INIT_BAL*2);
|
||||
token1.mint(address(this), INIT_BAL*2);
|
||||
token2.mint(address(this), INIT_BAL*2);
|
||||
token0.approve(address(planner), INIT_BAL*2);
|
||||
token1.approve(address(planner), INIT_BAL*2);
|
||||
token2.approve(address(planner), INIT_BAL*2);
|
||||
vm.prank(planner.owner());
|
||||
(pool,) = planner.newPool("LP", "LP", tokens, kappa, feePpm, feePpm, false,
|
||||
address(this), address(this), deposits, 0, 0);
|
||||
|
||||
// Deploy pool with zero fees for exact balance matching
|
||||
poolZeroFee = Deploy.newPartyPool(address(this), "LP_ZERO", "LP_ZERO", tokens, kappa, 0, 0, false);
|
||||
|
||||
// Mint additional tokens for zero-fee pool
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
token1.mint(address(this), INIT_BAL);
|
||||
token2.mint(address(this), INIT_BAL);
|
||||
|
||||
// Transfer to zero-fee pool
|
||||
token0.transfer(address(poolZeroFee), INIT_BAL);
|
||||
token1.transfer(address(poolZeroFee), INIT_BAL);
|
||||
token2.transfer(address(poolZeroFee), INIT_BAL);
|
||||
|
||||
// Initialize zero-fee pool
|
||||
poolZeroFee.initialMint(address(this), INIT_BAL * tokens.length * 10**18);
|
||||
vm.prank(planner.owner());
|
||||
(poolZeroFee,) = planner.newPool("LP_ZERO", "LP_ZERO", tokens, kappa, 0, 0, false,
|
||||
address(this), address(this), deposits, 0, 0);
|
||||
|
||||
// Mint tokens to alice and bob for testing
|
||||
token0.mint(alice, INIT_BAL);
|
||||
@@ -153,7 +161,7 @@ contract FundingTest is Test {
|
||||
token2.mint(bob, INIT_BAL);
|
||||
|
||||
// Deploy callback contract
|
||||
callbackContract = new SwapCallbackContract(address(pool));
|
||||
callbackContract = new SwapCallbackContract(address(pool), planner);
|
||||
|
||||
info = Deploy.newInfo();
|
||||
}
|
||||
@@ -184,7 +192,8 @@ contract FundingTest is Test {
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap
|
||||
false, // unwrap
|
||||
''
|
||||
);
|
||||
|
||||
// Verify amounts
|
||||
@@ -225,7 +234,7 @@ contract FundingTest is Test {
|
||||
uint256 poolToken1Before = token1.balanceOf(address(poolZeroFee));
|
||||
|
||||
// Execute swap
|
||||
(uint256 amountIn, uint256 amountOut, uint256 fee) = poolZeroFee.swap(
|
||||
(, uint256 amountOut, uint256 fee) = poolZeroFee.swap(
|
||||
alice,
|
||||
Funding.PREFUNDING,
|
||||
bob,
|
||||
@@ -234,7 +243,8 @@ contract FundingTest is Test {
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
|
||||
// With zero fees, fee should be 0
|
||||
@@ -270,7 +280,8 @@ contract FundingTest is Test {
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
@@ -310,7 +321,8 @@ contract FundingTest is Test {
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap
|
||||
false, // unwrap
|
||||
''
|
||||
);
|
||||
|
||||
// Verify amounts
|
||||
@@ -335,7 +347,7 @@ contract FundingTest is Test {
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
// Setup callback for zero-fee pool
|
||||
SwapCallbackContract zeroFeeCallback = new SwapCallbackContract(address(poolZeroFee));
|
||||
SwapCallbackContract zeroFeeCallback = new SwapCallbackContract(address(poolZeroFee), planner);
|
||||
zeroFeeCallback.setTokenSource(alice);
|
||||
zeroFeeCallback.setShouldFail(false);
|
||||
|
||||
@@ -356,7 +368,8 @@ contract FundingTest is Test {
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
|
||||
// With zero fees, fee should be 0
|
||||
@@ -387,7 +400,8 @@ contract FundingTest is Test {
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
@@ -395,52 +409,95 @@ contract FundingTest is Test {
|
||||
Validation Against swapAmounts()
|
||||
---------------------- */
|
||||
|
||||
|
||||
function createTestPools2() public returns (IPartyPool testPool1, IPartyPool testPool2) {
|
||||
// Create two identical test pools
|
||||
IERC20[] memory tokens = new IERC20[](3);
|
||||
tokens[0] = IERC20(address(token0));
|
||||
tokens[1] = IERC20(address(token1));
|
||||
tokens[2] = IERC20(address(token2));
|
||||
|
||||
uint256 feePpm = 1000;
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
uint256[] memory deposits = new uint256[](tokens.length);
|
||||
for(uint256 i=0; i<deposits.length; i++)
|
||||
deposits[i] = INIT_BAL;
|
||||
|
||||
token0.mint(address(this), INIT_BAL*2);
|
||||
token1.mint(address(this), INIT_BAL*2);
|
||||
token2.mint(address(this), INIT_BAL*2);
|
||||
token0.approve(address(planner), INIT_BAL*2);
|
||||
token1.approve(address(planner), INIT_BAL*2);
|
||||
token2.approve(address(planner), INIT_BAL*2);
|
||||
vm.prank(planner.owner());
|
||||
(testPool1,) = planner.newPool("LP_TEST_1", "LP_TEST_1", tokens, kappa, feePpm, feePpm, false,
|
||||
address(this), address(this), deposits, INIT_BAL * tokens.length * 10**18, 0);
|
||||
vm.prank(planner.owner());
|
||||
(testPool2,) = planner.newPool("LP_TEST_2", "LP_TEST_2", tokens, kappa, feePpm, feePpm, false,
|
||||
address(this), address(this), deposits, INIT_BAL * tokens.length * 10**18, 0);
|
||||
}
|
||||
|
||||
|
||||
function createTestPools3() public returns (IPartyPool testPool1, IPartyPool testPool2, IPartyPool testPool3) {
|
||||
// Create two identical test pools
|
||||
IERC20[] memory tokens = new IERC20[](3);
|
||||
tokens[0] = IERC20(address(token0));
|
||||
tokens[1] = IERC20(address(token1));
|
||||
tokens[2] = IERC20(address(token2));
|
||||
|
||||
uint256 feePpm = 1000;
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
uint256[] memory deposits = new uint256[](tokens.length);
|
||||
for(uint256 i=0; i<deposits.length; i++)
|
||||
deposits[i] = INIT_BAL;
|
||||
|
||||
token0.mint(address(this), INIT_BAL*3);
|
||||
token1.mint(address(this), INIT_BAL*3);
|
||||
token2.mint(address(this), INIT_BAL*3);
|
||||
token0.approve(address(planner), INIT_BAL*3);
|
||||
token1.approve(address(planner), INIT_BAL*3);
|
||||
token2.approve(address(planner), INIT_BAL*3);
|
||||
vm.prank(planner.owner());
|
||||
(testPool1,) = planner.newPool("LP_TEST_1", "LP_TEST_1", tokens, kappa, feePpm, feePpm, false,
|
||||
address(this), address(this), deposits, INIT_BAL * tokens.length * 10**18, 0);
|
||||
vm.prank(planner.owner());
|
||||
(testPool2,) = planner.newPool("LP_TEST_2", "LP_TEST_2", tokens, kappa, feePpm, feePpm, false,
|
||||
address(this), address(this), deposits, INIT_BAL * tokens.length * 10**18, 0);
|
||||
vm.prank(planner.owner());
|
||||
(testPool3,) = planner.newPool("LP_TEST_3", "LP_TEST_3", tokens, kappa, feePpm, feePpm, false,
|
||||
address(this), address(this), deposits, INIT_BAL * tokens.length * 10**18, 0);
|
||||
}
|
||||
|
||||
|
||||
/// @notice Verify that pre-funded swap amounts match swapAmounts() view predictions
|
||||
function testPreFundingMatchesSwapAmountsView() public {
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
(IPartyPool testPool1, IPartyPool testPool2) = createTestPools2();
|
||||
|
||||
// Perform a reference swap with USE_APPROVALS to get expected amounts
|
||||
vm.startPrank(bob);
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
|
||||
(uint256 refAmountIn, uint256 refAmountOut, uint256 refFee) = pool.swap(
|
||||
token0.approve(address(testPool1), type(uint256).max);
|
||||
|
||||
(uint256 refAmountIn, uint256 refAmountOut, uint256 refFee) = testPool1.swap(
|
||||
bob,
|
||||
Funding.APPROVALS,
|
||||
Funding.APPROVAL,
|
||||
bob,
|
||||
0,
|
||||
1,
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
// Reset pool state by creating a fresh pool with identical parameters
|
||||
IERC20[] memory tokens = new IERC20[](3);
|
||||
tokens[0] = IERC20(address(token0));
|
||||
tokens[1] = IERC20(address(token1));
|
||||
tokens[2] = IERC20(address(token2));
|
||||
|
||||
uint256 feePpm = 1000;
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool testPool = Deploy.newPartyPool(address(this), "LP_TEST", "LP_TEST", tokens, kappa, feePpm, feePpm, false);
|
||||
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
token1.mint(address(this), INIT_BAL);
|
||||
token2.mint(address(this), INIT_BAL);
|
||||
|
||||
token0.transfer(address(testPool), INIT_BAL);
|
||||
token1.transfer(address(testPool), INIT_BAL);
|
||||
token2.transfer(address(testPool), INIT_BAL);
|
||||
|
||||
testPool.initialMint(address(this), INIT_BAL * tokens.length * 10**18);
|
||||
|
||||
// Now test pre-funding with same initial state
|
||||
// Now perform a swap using the prefunding method on the second pool
|
||||
vm.startPrank(alice);
|
||||
token0.transfer(address(testPool), maxIn);
|
||||
token0.transfer(address(testPool2), maxIn);
|
||||
|
||||
(uint256 preAmountIn, uint256 preAmountOut, uint256 preFee) = testPool.swap(
|
||||
(uint256 preAmountIn, uint256 preAmountOut, uint256 preFee) = testPool2.swap(
|
||||
alice,
|
||||
Funding.PREFUNDING,
|
||||
alice,
|
||||
@@ -449,7 +506,8 @@ contract FundingTest is Test {
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
@@ -463,45 +521,28 @@ contract FundingTest is Test {
|
||||
function testCallbackMatchesSwapAmountsView() public {
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
(IPartyPool testPool1, IPartyPool testPool2) = createTestPools2();
|
||||
|
||||
// Perform a reference swap
|
||||
vm.startPrank(bob);
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
token0.approve(address(testPool1), type(uint256).max);
|
||||
|
||||
(uint256 refAmountIn, uint256 refAmountOut, uint256 refFee) = pool.swap(
|
||||
(uint256 refAmountIn, uint256 refAmountOut, uint256 refFee) = testPool1.swap(
|
||||
bob,
|
||||
Funding.APPROVALS,
|
||||
Funding.APPROVAL,
|
||||
bob,
|
||||
0,
|
||||
1,
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
// Create fresh pool for callback test
|
||||
IERC20[] memory tokens = new IERC20[](3);
|
||||
tokens[0] = IERC20(address(token0));
|
||||
tokens[1] = IERC20(address(token1));
|
||||
tokens[2] = IERC20(address(token2));
|
||||
|
||||
uint256 feePpm = 1000;
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool testPool = Deploy.newPartyPool(address(this), "LP_TEST2", "LP_TEST2", tokens, kappa, feePpm, feePpm, false);
|
||||
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
token1.mint(address(this), INIT_BAL);
|
||||
token2.mint(address(this), INIT_BAL);
|
||||
|
||||
token0.transfer(address(testPool), INIT_BAL);
|
||||
token1.transfer(address(testPool), INIT_BAL);
|
||||
token2.transfer(address(testPool), INIT_BAL);
|
||||
|
||||
testPool.initialMint(address(this), INIT_BAL * tokens.length * 10**18);
|
||||
|
||||
// Setup callback for test pool
|
||||
SwapCallbackContract testCallback = new SwapCallbackContract(address(testPool));
|
||||
SwapCallbackContract testCallback = new SwapCallbackContract(address(testPool2), planner);
|
||||
testCallback.setTokenSource(alice);
|
||||
testCallback.setShouldFail(false);
|
||||
|
||||
@@ -510,7 +551,7 @@ contract FundingTest is Test {
|
||||
vm.stopPrank();
|
||||
|
||||
// Test callback with same initial state
|
||||
(uint256 cbAmountIn, uint256 cbAmountOut, uint256 cbFee) = testPool.swap(
|
||||
(uint256 cbAmountIn, uint256 cbAmountOut, uint256 cbFee) = testPool2.swap(
|
||||
address(testCallback),
|
||||
CALLBACK,
|
||||
alice,
|
||||
@@ -519,7 +560,8 @@ contract FundingTest is Test {
|
||||
maxIn,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
false,
|
||||
''
|
||||
);
|
||||
|
||||
// Callback amounts should match reference swap amounts
|
||||
@@ -538,56 +580,26 @@ contract FundingTest is Test {
|
||||
for (uint i = 0; i < swapAmounts.length; i++) {
|
||||
uint256 swapAmount = swapAmounts[i];
|
||||
|
||||
// Create three identical pools
|
||||
IERC20[] memory tokens = new IERC20[](3);
|
||||
tokens[0] = IERC20(address(token0));
|
||||
tokens[1] = IERC20(address(token1));
|
||||
tokens[2] = IERC20(address(token2));
|
||||
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
|
||||
PartyPool poolApproval = Deploy.newPartyPool(address(this), "LP_A", "LP_A", tokens, kappa, 0, 0, false);
|
||||
PartyPool poolPreFund = Deploy.newPartyPool(address(this), "LP_P", "LP_P", tokens, kappa, 0, 0, false);
|
||||
PartyPool poolCallback = Deploy.newPartyPool(address(this), "LP_C", "LP_C", tokens, kappa, 0, 0, false);
|
||||
(IPartyPool poolApproval, IPartyPool poolPreFund, IPartyPool poolCallback) = createTestPools3();
|
||||
|
||||
// Initialize all three pools identically
|
||||
token0.mint(address(this), INIT_BAL * 3);
|
||||
token1.mint(address(this), INIT_BAL * 3);
|
||||
token2.mint(address(this), INIT_BAL * 3);
|
||||
|
||||
token0.transfer(address(poolApproval), INIT_BAL);
|
||||
token1.transfer(address(poolApproval), INIT_BAL);
|
||||
token2.transfer(address(poolApproval), INIT_BAL);
|
||||
poolApproval.initialMint(address(this), 0);
|
||||
|
||||
token0.transfer(address(poolPreFund), INIT_BAL);
|
||||
token1.transfer(address(poolPreFund), INIT_BAL);
|
||||
token2.transfer(address(poolPreFund), INIT_BAL);
|
||||
poolPreFund.initialMint(address(this), 0);
|
||||
|
||||
token0.transfer(address(poolCallback), INIT_BAL);
|
||||
token1.transfer(address(poolCallback), INIT_BAL);
|
||||
token2.transfer(address(poolCallback), INIT_BAL);
|
||||
poolCallback.initialMint(address(this), 0);
|
||||
|
||||
// Test with USE_APPROVALS (bytes4(0) with approvals)
|
||||
// Test with APPROVALS
|
||||
vm.startPrank(alice);
|
||||
token0.approve(address(poolApproval), type(uint256).max);
|
||||
(uint256 apprIn, uint256 apprOut, ) = poolApproval.swap(
|
||||
alice, Funding.APPROVALS, alice, 0, 1, swapAmount, 0, 0, false
|
||||
alice, Funding.APPROVAL, alice, 0, 1, swapAmount, 0, 0, false, ''
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
// Test with PRE_FUNDED (bytes4(0) with pre-funding)
|
||||
// Test with PREFUNDING
|
||||
vm.startPrank(alice);
|
||||
token0.transfer(address(poolPreFund), swapAmount);
|
||||
(uint256 preIn, uint256 preOut, ) = poolPreFund.swap(
|
||||
alice, Funding.PREFUNDING, alice, 0, 1, swapAmount, 0, 0, false
|
||||
alice, Funding.PREFUNDING, alice, 0, 1, swapAmount, 0, 0, false, ''
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
// Test with CALLBACK
|
||||
SwapCallbackContract cb = new SwapCallbackContract(address(poolCallback));
|
||||
SwapCallbackContract cb = new SwapCallbackContract(address(poolCallback), planner);
|
||||
cb.setTokenSource(alice);
|
||||
cb.setShouldFail(false);
|
||||
|
||||
@@ -596,7 +608,7 @@ contract FundingTest is Test {
|
||||
vm.stopPrank();
|
||||
|
||||
(uint256 cbIn, uint256 cbOut, ) = poolCallback.swap(
|
||||
address(cb), CALLBACK, alice, 0, 1, swapAmount, 0, 0, false
|
||||
address(cb), CALLBACK, alice, 0, 1, swapAmount, 0, 0, false, ''
|
||||
);
|
||||
|
||||
// All three methods should produce identical results
|
||||
|
||||
151
test/GasTest.sol
151
test/GasTest.sol
@@ -13,11 +13,13 @@ import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.s
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {Funding} from "../src/Funding.sol";
|
||||
import {IPartyPlanner} from "../src/IPartyPlanner.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {IPartySwapCallback} from "../src/IPartySwapCallback.sol";
|
||||
import {LMSRStabilized} from "../src/LMSRStabilized.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {PartySwapCallbackVerifier} from "../src/PartySwapCallbackVerifier.sol";
|
||||
import {Deploy} from "./Deploy.sol";
|
||||
import {TestERC20, FlashBorrower} from "./GasTest.sol";
|
||||
import {TestERC20, GasHarness, FlashBorrower} from "./GasTest.sol";
|
||||
|
||||
/* solhint-disable erc20-unchecked-transfer */
|
||||
|
||||
@@ -103,12 +105,58 @@ contract TestERC20 is ERC20 {
|
||||
}
|
||||
}
|
||||
|
||||
contract GasHarness is IPartySwapCallback {
|
||||
// In order to compare like-for-like, we need to include the token transfers in a single external function for gas measurement
|
||||
|
||||
using SafeERC20 for ERC20;
|
||||
|
||||
IPartyPlanner immutable private planner;
|
||||
|
||||
constructor(IPartyPlanner planner_) {
|
||||
planner = planner_;
|
||||
}
|
||||
|
||||
function swapApproval(
|
||||
IPartyPool pool, IERC20 tokenIn, address /*payer*/, bytes4 fundingSelector, address receiver, uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex, uint256 maxAmountIn, int128 limitPrice, uint256 deadline, bool unwrap
|
||||
) external payable returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
// pool moves coins
|
||||
tokenIn.approve(address(pool), type(uint256).max);
|
||||
(amountIn, amountOut, inFee) = pool.swap{value:msg.value}(address(this), fundingSelector, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, unwrap, '');
|
||||
tokenIn.approve(address(pool), 0);
|
||||
}
|
||||
|
||||
function swapPrefund(
|
||||
IPartyPool pool, address /*payer*/, bytes4 fundingSelector, address receiver, uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex, uint256 maxAmountIn, int128 limitPrice, uint256 deadline, bool unwrap
|
||||
) external payable returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
// Prefund the pool
|
||||
IERC20(pool.token(inputTokenIndex)).transfer(address(pool), maxAmountIn);
|
||||
return pool.swap{value:msg.value}(address(0), fundingSelector, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, unwrap, '');
|
||||
}
|
||||
|
||||
function swapCallback(
|
||||
IPartyPool pool, address /*payer*/, bytes4 /*fundingSelector*/, address receiver, uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex, uint256 maxAmountIn, int128 limitPrice, uint256 deadline, bool unwrap
|
||||
) external payable returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
return pool.swap{value:msg.value}(address(this), this.liquidityPartySwapCallback.selector, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, unwrap, '');
|
||||
}
|
||||
|
||||
function liquidityPartySwapCallback(bytes32 nonce, IERC20 token, uint256 amount, bytes memory) external {
|
||||
PartySwapCallbackVerifier.verifyCallback(planner, nonce);
|
||||
token.transfer(msg.sender, amount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @notice Gas testing contract for PartyPool - contains all gas measurement tests
|
||||
contract GasTest is Test {
|
||||
using ABDKMath64x64 for int128;
|
||||
using SafeERC20 for TestERC20;
|
||||
|
||||
PartyPlanner internal planner;
|
||||
GasHarness internal harness;
|
||||
|
||||
IPartyPlanner internal planner;
|
||||
IPartyPool internal pool2;
|
||||
IPartyPool internal pool10;
|
||||
IPartyPool internal pool20;
|
||||
@@ -138,6 +186,7 @@ contract GasTest is Test {
|
||||
|
||||
// Mint initial balances for pool initialization and test users
|
||||
token.mint(address(this), INIT_BAL);
|
||||
token.mint(address(harness), INIT_BAL);
|
||||
token.mint(alice, INIT_BAL);
|
||||
token.mint(bob, INIT_BAL);
|
||||
}
|
||||
@@ -165,7 +214,7 @@ contract GasTest is Test {
|
||||
}
|
||||
|
||||
/// @notice Helper to create a pool with the stable-pair optimization enabled
|
||||
function createPoolStable(uint256 numTokens) internal returns (IPartyPool) {
|
||||
function createPoolStable(uint256 numTokens) internal returns (IPartyPool pool) {
|
||||
// Deploy _tokens dynamically
|
||||
address[] memory tokens = new address[](numTokens);
|
||||
uint256[] memory bases = new uint256[](numTokens);
|
||||
@@ -178,6 +227,7 @@ contract GasTest is Test {
|
||||
|
||||
// Mint initial balances for pool initialization and test users
|
||||
token.mint(address(this), INIT_BAL);
|
||||
token.mint(address(harness), INIT_BAL);
|
||||
token.mint(alice, INIT_BAL);
|
||||
token.mint(bob, INIT_BAL);
|
||||
}
|
||||
@@ -191,17 +241,15 @@ contract GasTest is Test {
|
||||
ierc20Tokens[i] = IERC20(tokens[i]);
|
||||
}
|
||||
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage);
|
||||
IPartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, computedKappa, feePpm, feePpm, true);
|
||||
|
||||
// Transfer initial deposit amounts into pool before initial mint
|
||||
uint256[] memory initialBalances = new uint256[](numTokens);
|
||||
for (uint256 i = 0; i < numTokens; i++) {
|
||||
TestERC20(tokens[i]).transfer(address(newPool), INIT_BAL);
|
||||
initialBalances[i] = INIT_BAL;
|
||||
ierc20Tokens[i].approve(address(planner), INIT_BAL);
|
||||
}
|
||||
|
||||
// Perform initial mint (initial deposit); receiver is this contract
|
||||
newPool.initialMint(address(this), 0);
|
||||
|
||||
return newPool;
|
||||
vm.prank(planner.owner());
|
||||
(pool, ) = planner.newPool(poolName, poolName, ierc20Tokens, computedKappa, feePpm, feePpm, true,
|
||||
address(this), address(this), initialBalances, 0, 0);
|
||||
}
|
||||
|
||||
function setUp() public {
|
||||
@@ -210,6 +258,8 @@ contract GasTest is Test {
|
||||
|
||||
planner = Deploy.newPartyPlanner();
|
||||
|
||||
harness = new GasHarness(planner);
|
||||
|
||||
// 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
|
||||
@@ -238,39 +288,35 @@ contract GasTest is Test {
|
||||
|
||||
/// @notice Helper function: perform 10 swaps back-and-forth between the first two _tokens.
|
||||
function _performSwapGasTest(IPartyPool testPool) internal {
|
||||
_performSwapGasTest(testPool, Funding.APPROVALS);
|
||||
_performSwapGasTest(testPool, Funding.APPROVAL);
|
||||
}
|
||||
|
||||
function sendTokensCallback(IERC20 token, uint256 amount) external {
|
||||
// verify the caller
|
||||
require(planner.getPoolSupported(msg.sender), 'Not a LiqP pool');
|
||||
token.transferFrom( alice, msg.sender, amount);
|
||||
|
||||
function _doSwap(
|
||||
IPartyPool pool,
|
||||
address payer,
|
||||
bytes4 fundingSelector,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
) internal returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
if (fundingSelector == Funding.APPROVAL)
|
||||
return harness.swapApproval{value:msg.value}(pool, pool.token(inputTokenIndex), payer, fundingSelector, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, unwrap);
|
||||
if (fundingSelector == Funding.PREFUNDING) {
|
||||
pool.token(inputTokenIndex).transfer(address(harness), maxAmountIn);
|
||||
return harness.swapPrefund{value:msg.value}(pool, payer, fundingSelector, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, unwrap);
|
||||
}
|
||||
else
|
||||
return harness.swapCallback{value:msg.value}(pool, payer, fundingSelector, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, unwrap);
|
||||
}
|
||||
|
||||
function _performSwapGasTest(IPartyPool testPool, bytes4 fundingSelector) internal {
|
||||
IERC20[] memory tokens = testPool.allTokens();
|
||||
require(tokens.length >= 2, "Pool must have at least 2 tokens");
|
||||
address payer;
|
||||
address spender;
|
||||
|
||||
if (fundingSelector == Funding.PREFUNDING) {
|
||||
payer = address(this);
|
||||
spender = address(this);
|
||||
}
|
||||
else if (fundingSelector == Funding.APPROVALS) {
|
||||
payer = alice;
|
||||
spender = address(testPool);
|
||||
}
|
||||
else {
|
||||
payer = address(this);
|
||||
spender = address(this);
|
||||
}
|
||||
TestERC20 token0 = TestERC20(address(tokens[0]));
|
||||
TestERC20 token1 = TestERC20(address(tokens[1]));
|
||||
vm.prank(alice);
|
||||
token0.approve(spender, type(uint256).max);
|
||||
vm.prank(alice);
|
||||
token1.approve(spender, type(uint256).max);
|
||||
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
@@ -278,15 +324,11 @@ contract GasTest is Test {
|
||||
vm.startPrank(alice);
|
||||
for (uint256 i = 0; i < 20; i++) {
|
||||
if (i % 2 == 0) {
|
||||
if (fundingSelector == Funding.PREFUNDING)
|
||||
token0.transfer(address(testPool), maxIn);
|
||||
// swap token0 -> token1
|
||||
testPool.swap(payer, fundingSelector, alice, 0, 1, maxIn, 0, 0, false);
|
||||
_doSwap(testPool, alice, fundingSelector, alice, 0, 1, maxIn, 0, 0, false);
|
||||
} else {
|
||||
// swap token1 -> token0
|
||||
if (fundingSelector == Funding.PREFUNDING)
|
||||
token1.transfer(address(testPool), maxIn);
|
||||
testPool.swap(payer, fundingSelector, alice, 1, 0, maxIn, 0, 0, false);
|
||||
_doSwap( testPool, alice, fundingSelector, alice, 1, 0, maxIn, 0, 0, false);
|
||||
}
|
||||
// shake up the bits
|
||||
maxIn *= 787;
|
||||
@@ -306,20 +348,29 @@ contract GasTest is Test {
|
||||
}
|
||||
|
||||
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two _tokens in the 10-token pool using the callback funding method.
|
||||
function testSwapGasCallback() public {
|
||||
_performSwapGasTest(pool10, this.sendTokensCallback.selector);
|
||||
function testSwapGasCallback10() public {
|
||||
_performSwapGasTest(pool10, IPartySwapCallback.liquidityPartySwapCallback.selector);
|
||||
}
|
||||
|
||||
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two _tokens in the 10-token pool using the callback funding method.
|
||||
function testSwapGasPrefunding() public {
|
||||
function testSwapGasPrefunding10() public {
|
||||
_performSwapGasTest(pool10, Funding.PREFUNDING);
|
||||
}
|
||||
|
||||
function testSwapGasPrefunding20() public {
|
||||
_performSwapGasTest(pool20, Funding.PREFUNDING);
|
||||
}
|
||||
|
||||
/// @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 10-token pool using the callback funding method.
|
||||
function testSwapGasCallback20() public {
|
||||
_performSwapGasTest(pool20, IPartySwapCallback.liquidityPartySwapCallback.selector);
|
||||
}
|
||||
|
||||
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two _tokens in the 100-token pool.
|
||||
function testSwapGasFifty() public {
|
||||
_performSwapGasTest(pool50);
|
||||
@@ -331,6 +382,12 @@ contract GasTest is Test {
|
||||
_performSwapGasTest(stablePair);
|
||||
}
|
||||
|
||||
/// @notice Gas measurement: perform 10 swaps back-and-forth on a 2-token stable pair (stable-path enabled)
|
||||
function testSwapGasPrefundingSP() public {
|
||||
IPartyPool stablePair = createPoolStable(2);
|
||||
_performSwapGasTest(stablePair, IPartySwapCallback.liquidityPartySwapCallback.selector);
|
||||
}
|
||||
|
||||
/// @notice Gas-style test: alternate swapMint then burnSwap on a 2-token stable pair
|
||||
function testSwapMintBurnSwapGasStablePair() public {
|
||||
IPartyPool stablePair = createPoolStable(2);
|
||||
|
||||
@@ -12,9 +12,9 @@ import {Test} from "../lib/forge-std/src/Test.sol";
|
||||
import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {Funding} from "../src/Funding.sol";
|
||||
import {IPartyInfo} from "../src/IPartyInfo.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {LMSRStabilized} from "../src/LMSRStabilized.sol";
|
||||
import {PartyInfo} from "../src/PartyInfo.sol";
|
||||
import {PartyPool} from "../src/PartyPool.sol";
|
||||
import {Deploy} from "./Deploy.sol";
|
||||
import {TestERC20Native} from "./NativeTest.t.sol";
|
||||
import {WETH9} from "./WETH9.sol";
|
||||
@@ -46,8 +46,8 @@ contract NativeTest is Test {
|
||||
TestERC20Native token0;
|
||||
TestERC20Native token1;
|
||||
WETH9 weth; // WETH is our third token
|
||||
PartyPool pool;
|
||||
PartyInfo info;
|
||||
IPartyPool pool;
|
||||
IPartyInfo info;
|
||||
|
||||
address alice;
|
||||
address bob;
|
||||
@@ -78,9 +78,6 @@ contract NativeTest is Test {
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
token1.mint(address(this), INIT_BAL);
|
||||
|
||||
// For WETH, we deposit native currency to get wrapped _tokens
|
||||
weth.deposit{value: INIT_BAL}();
|
||||
|
||||
// Configure LMSR parameters
|
||||
tradeFrac = ABDKMath64x64.divu(100, 10_000); // 0.01
|
||||
targetSlippage = ABDKMath64x64.divu(10, 10_000); // 0.001
|
||||
@@ -100,15 +97,7 @@ contract NativeTest is Test {
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, kappa, feePpm, feePpm, weth, false);
|
||||
|
||||
// Transfer initial deposit amounts into pool
|
||||
token0.transfer(address(pool), INIT_BAL);
|
||||
token1.transfer(address(pool), INIT_BAL);
|
||||
weth.transfer(address(pool), INIT_BAL);
|
||||
|
||||
// Perform initial mint
|
||||
pool.initialMint(address(this), 0);
|
||||
pool = Deploy.newPartyPool("LP", "LP", tokens, kappa, feePpm, feePpm, weth, false, INIT_BAL, 0);
|
||||
|
||||
// Mint _tokens to alice and bob for testing
|
||||
token0.mint(alice, INIT_BAL);
|
||||
@@ -148,14 +137,15 @@ contract NativeTest is Test {
|
||||
// Send native currency with {value: maxIn}
|
||||
(uint256 amountIn, uint256 amountOut, ) = pool.swap{value: maxIn}(
|
||||
alice, // payer
|
||||
Funding.APPROVALS,
|
||||
Funding.APPROVAL,
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap (output is not WETH, so false)
|
||||
false, // unwrap (output is not WETH, so false)
|
||||
''
|
||||
);
|
||||
|
||||
// Verify amounts
|
||||
@@ -186,14 +176,15 @@ contract NativeTest is Test {
|
||||
// Execute swap: token0 (index 0) -> WETH (index 2) with unwrap=true
|
||||
(uint256 amountIn, uint256 amountOut, ) = pool.swap(
|
||||
alice, // payer
|
||||
Funding.APPROVALS, // no selector: use ERC20 approvals
|
||||
Funding.APPROVAL, // no selector: use ERC20 approvals
|
||||
alice, // receiver
|
||||
0, // inputTokenIndex (token0)
|
||||
2, // outputTokenIndex (WETH)
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
true // unwrap (receive native currency instead of WETH)
|
||||
true, // unwrap (receive native currency instead of WETH)
|
||||
''
|
||||
);
|
||||
|
||||
// Verify amounts
|
||||
@@ -222,14 +213,15 @@ contract NativeTest is Test {
|
||||
// Execute swap with excess native currency
|
||||
(uint256 amountIn, , ) = pool.swap{value: totalSent}(
|
||||
alice, // payer
|
||||
Funding.APPROVALS,
|
||||
Funding.APPROVAL,
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap
|
||||
false, // unwrap
|
||||
''
|
||||
);
|
||||
|
||||
// Verify that only amountIn was used, and excess was refunded
|
||||
@@ -253,12 +245,14 @@ contract NativeTest is Test {
|
||||
uint256 largeAmount = 100_000;
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swapToLimit{value: largeAmount}(
|
||||
alice, // payer
|
||||
Funding.APPROVAL,
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
limitPrice, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap
|
||||
false, // unwrap
|
||||
''
|
||||
);
|
||||
|
||||
assertTrue(amountInUsed > 0, "expected some input used for swapToLimit");
|
||||
@@ -283,12 +277,14 @@ contract NativeTest is Test {
|
||||
// Execute swapToLimit: token0 (index 0) -> WETH (index 2) with unwrap=true
|
||||
(uint256 amountInUsed, uint256 amountOut, /*uint256 fee*/) = pool.swapToLimit(
|
||||
alice, // payer
|
||||
Funding.APPROVAL,
|
||||
alice, // receiver
|
||||
0, // inputTokenIndex (token0)
|
||||
2, // outputTokenIndex (WETH)
|
||||
limitPrice, // limitPrice
|
||||
0, // deadline
|
||||
true // unwrap (receive native currency)
|
||||
true, // unwrap (receive native currency)
|
||||
''
|
||||
);
|
||||
|
||||
assertTrue(amountInUsed > 0, "expected some input used");
|
||||
@@ -551,14 +547,14 @@ contract NativeTest is Test {
|
||||
// 2. Swap native currency for token0
|
||||
uint256 swapAmount = 5_000;
|
||||
(, uint256 amountOut, ) = pool.swap{value: swapAmount}(
|
||||
alice,Funding.APPROVALS,alice, 2, 0, swapAmount, 0, 0, false
|
||||
alice,Funding.APPROVAL,alice, 2, 0, swapAmount, 0, 0, false, ''
|
||||
);
|
||||
assertTrue(amountOut > 0, "Should receive token0");
|
||||
|
||||
// 3. Swap token0 back to native currency
|
||||
uint256 token0Balance = token0.balanceOf(alice);
|
||||
(, uint256 swapOut2, ) = pool.swap(
|
||||
alice, Funding.APPROVALS, alice, 0, 2, token0Balance / 2, 0, 0, true
|
||||
alice, Funding.APPROVAL, alice, 0, 2, token0Balance / 2, 0, 0, true, ''
|
||||
);
|
||||
assertTrue(swapOut2 > 0, "Should receive native currency");
|
||||
|
||||
@@ -585,7 +581,7 @@ contract NativeTest is Test {
|
||||
|
||||
// Swap token0 -> WETH without unwrap
|
||||
(, uint256 amountOut, ) = pool.swap(
|
||||
alice, Funding.APPROVALS, alice, 0, 2, maxIn, 0, 0, false // unwrap=false
|
||||
alice, Funding.APPROVAL, alice, 0, 2, maxIn, 0, 0, false, ''
|
||||
);
|
||||
|
||||
assertTrue(amountOut > 0, "Should receive WETH tokens");
|
||||
@@ -606,7 +602,7 @@ contract NativeTest is Test {
|
||||
// Try to swap token0 (not WETH) by sending native currency - should revert
|
||||
vm.expectRevert();
|
||||
pool.swap{value: 10_000}(
|
||||
alice, Funding.APPROVALS, alice, 0, 1, 10_000, 0, 0, false
|
||||
alice, Funding.APPROVAL, alice, 0, 1, 10_000, 0, 0, false, ''
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
@@ -9,11 +9,10 @@ import {StdUtils} from "../lib/forge-std/src/StdUtils.sol";
|
||||
import {Test} from "../lib/forge-std/src/Test.sol";
|
||||
import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {Deploy} from "./Deploy.sol";
|
||||
import {IPartyPlanner} from "../src/IPartyPlanner.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {LMSRStabilized} from "../src/LMSRStabilized.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {PartyPool} from "../src/PartyPool.sol";
|
||||
import {Deploy} from "./Deploy.sol";
|
||||
import {MockERC20} from "./PartyPlanner.t.sol";
|
||||
|
||||
// Mock ERC20 token for testing
|
||||
@@ -34,7 +33,7 @@ contract MockERC20 is ERC20 {
|
||||
}
|
||||
|
||||
contract PartyPlannerTest is Test {
|
||||
PartyPlanner public planner;
|
||||
IPartyPlanner public planner;
|
||||
MockERC20 public tokenA;
|
||||
MockERC20 public tokenB;
|
||||
MockERC20 public tokenC;
|
||||
|
||||
@@ -13,9 +13,10 @@ import {IERC3156FlashBorrower} from "../lib/openzeppelin-contracts/contracts/int
|
||||
import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {Funding} from "../src/Funding.sol";
|
||||
import {IPartyInfo} from "../src/IPartyInfo.sol";
|
||||
import {IPartyPlanner} from "../src/IPartyPlanner.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {LMSRStabilized} from "../src/LMSRStabilized.sol";
|
||||
import {PartyInfo} from "../src/PartyInfo.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {PartyPool} from "../src/PartyPool.sol";
|
||||
import {Deploy} from "./Deploy.sol";
|
||||
import {TestERC20, FlashBorrower} from "./PartyPool.t.sol";
|
||||
@@ -118,10 +119,10 @@ contract PartyPoolTest is Test {
|
||||
TestERC20 token7;
|
||||
TestERC20 token8;
|
||||
TestERC20 token9;
|
||||
PartyPlanner planner;
|
||||
PartyPool pool;
|
||||
PartyPool pool10;
|
||||
PartyInfo info;
|
||||
IPartyPlanner planner;
|
||||
IPartyPool pool;
|
||||
IPartyPool pool10;
|
||||
IPartyInfo info;
|
||||
|
||||
address alice;
|
||||
address bob;
|
||||
@@ -175,16 +176,8 @@ contract PartyPoolTest is Test {
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
int128 kappa3 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, kappa3, feePpm, feePpm, false);
|
||||
|
||||
// Transfer initial deposit amounts into pool before initial mint (pool expects _tokens already in contract)
|
||||
// We deposit equal amounts INIT_BAL for each token
|
||||
token0.transfer(address(pool), INIT_BAL);
|
||||
token1.transfer(address(pool), INIT_BAL);
|
||||
token2.transfer(address(pool), INIT_BAL);
|
||||
|
||||
// Perform initial mint (initial deposit); receiver is this contract
|
||||
pool.initialMint(address(this), INIT_BAL * tokens.length * 10**18);
|
||||
uint256 lpTokens = INIT_BAL * tokens.length * 10**18;
|
||||
pool = Deploy.newPartyPool("LP", "LP", tokens, kappa3, feePpm, feePpm, false, INIT_BAL, lpTokens);
|
||||
|
||||
// Set up pool10 with 10 _tokens
|
||||
IERC20[] memory tokens10 = new IERC20[](10);
|
||||
@@ -200,34 +193,7 @@ contract PartyPoolTest is Test {
|
||||
tokens10[9] = IERC20(address(token9));
|
||||
|
||||
int128 kappa10 = LMSRStabilized.computeKappaFromSlippage(tokens10.length, tradeFrac, targetSlippage);
|
||||
pool10 = Deploy.newPartyPool(address(this), "LP10", "LP10", tokens10, kappa10, feePpm, feePpm, false);
|
||||
|
||||
// Mint additional _tokens for pool10 initial deposit
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
token1.mint(address(this), INIT_BAL);
|
||||
token2.mint(address(this), INIT_BAL);
|
||||
token3.mint(address(this), INIT_BAL);
|
||||
token4.mint(address(this), INIT_BAL);
|
||||
token5.mint(address(this), INIT_BAL);
|
||||
token6.mint(address(this), INIT_BAL);
|
||||
token7.mint(address(this), INIT_BAL);
|
||||
token8.mint(address(this), INIT_BAL);
|
||||
token9.mint(address(this), INIT_BAL);
|
||||
|
||||
// Transfer initial deposit amounts into pool10
|
||||
token0.transfer(address(pool10), INIT_BAL);
|
||||
token1.transfer(address(pool10), INIT_BAL);
|
||||
token2.transfer(address(pool10), INIT_BAL);
|
||||
token3.transfer(address(pool10), INIT_BAL);
|
||||
token4.transfer(address(pool10), INIT_BAL);
|
||||
token5.transfer(address(pool10), INIT_BAL);
|
||||
token6.transfer(address(pool10), INIT_BAL);
|
||||
token7.transfer(address(pool10), INIT_BAL);
|
||||
token8.transfer(address(pool10), INIT_BAL);
|
||||
token9.transfer(address(pool10), INIT_BAL);
|
||||
|
||||
// Perform initial mint for pool10
|
||||
pool10.initialMint(address(this), 0);
|
||||
pool10 = Deploy.newPartyPool("LP10", "LP10", tokens10, kappa10, feePpm, feePpm, false, INIT_BAL, 0);
|
||||
|
||||
// For later tests we will mint _tokens to alice/bob as needed
|
||||
token0.mint(alice, INIT_BAL);
|
||||
@@ -431,7 +397,7 @@ contract PartyPoolTest is Test {
|
||||
|
||||
// Execute swap: token0 -> token1
|
||||
vm.prank(alice);
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swap(alice, Funding.APPROVALS, bob, 0, 1, maxIn, 0, 0, false);
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swap(alice, Funding.APPROVAL, bob, 0, 1, maxIn, 0, 0, false, '');
|
||||
|
||||
// Amounts should be positive and not exceed provided max
|
||||
assertTrue(amountInUsed > 0, "expected some input used");
|
||||
@@ -460,7 +426,7 @@ contract PartyPoolTest is Test {
|
||||
|
||||
vm.prank(alice);
|
||||
vm.expectRevert(bytes("LMSR: limitPrice <= current price"));
|
||||
pool.swap(alice, Funding.APPROVALS, alice, 0, 1, 1000, limitPrice, 0, false);
|
||||
pool.swap(alice, Funding.APPROVAL, alice, 0, 1, 1000, limitPrice, 0, false, '');
|
||||
}
|
||||
|
||||
/// @notice swapToLimit should compute input needed to reach a slightly higher price and execute.
|
||||
@@ -472,7 +438,7 @@ contract PartyPoolTest is Test {
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
|
||||
vm.prank(alice);
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swapToLimit(alice, bob, 0, 1, limitPrice, 0, false);
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swapToLimit(alice, Funding.APPROVAL, bob, 0, 1, limitPrice, 0, false, '');
|
||||
|
||||
assertTrue(amountInUsed > 0, "expected some input used for swapToLimit");
|
||||
assertTrue(amountOut > 0, "expected some output for swapToLimit");
|
||||
@@ -984,32 +950,12 @@ contract PartyPoolTest is Test {
|
||||
|
||||
// Pool with default initialization (lpTokens = 0)
|
||||
int128 kappaDefault = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, kappaDefault, feePpm, feePpm, false);
|
||||
(IPartyPool poolDefault, uint256 lpDefault) = Deploy.newPartyPool2("LP_DEFAULT", "LP_DEFAULT", tokens, kappaDefault, feePpm, feePpm, false, INIT_BAL, 0);
|
||||
|
||||
// Pool with custom initialization (lpTokens = custom amount)
|
||||
int128 kappaCustom = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, kappaCustom, feePpm, feePpm, false);
|
||||
|
||||
// Mint additional _tokens for both pools
|
||||
token0.mint(address(this), INIT_BAL * 2);
|
||||
token1.mint(address(this), INIT_BAL * 2);
|
||||
token2.mint(address(this), INIT_BAL * 2);
|
||||
|
||||
// Transfer identical amounts to both pools
|
||||
token0.transfer(address(poolDefault), INIT_BAL);
|
||||
token1.transfer(address(poolDefault), INIT_BAL);
|
||||
token2.transfer(address(poolDefault), INIT_BAL);
|
||||
|
||||
token0.transfer(address(poolCustom), INIT_BAL);
|
||||
token1.transfer(address(poolCustom), INIT_BAL);
|
||||
token2.transfer(address(poolCustom), INIT_BAL);
|
||||
|
||||
// Initialize poolDefault with lpTokens = 0 (default behavior)
|
||||
uint256 lpDefault = poolDefault.initialMint(address(this), 0);
|
||||
|
||||
// Initialize poolCustom with custom lpTokens amount (5x the default)
|
||||
uint256 customLpAmount = lpDefault * 5;
|
||||
uint256 lpCustom = poolCustom.initialMint(address(this), customLpAmount);
|
||||
(IPartyPool poolCustom, uint256 lpCustom) = Deploy.newPartyPool2("LP_CUSTOM", "LP_CUSTOM", tokens, kappaCustom, feePpm, feePpm, false, INIT_BAL, customLpAmount);
|
||||
|
||||
// Verify the custom pool has the expected LP supply
|
||||
assertEq(lpCustom, customLpAmount, "Custom pool should have expected LP amount");
|
||||
@@ -1032,8 +978,8 @@ contract PartyPoolTest is Test {
|
||||
token0.approve(address(poolCustom), type(uint256).max);
|
||||
|
||||
// Perform identical swaps: token0 -> token1
|
||||
(uint256 amountInDefault, uint256 amountOutDefault, uint256 feeDefault) = poolDefault.swap(alice, Funding.APPROVALS, alice, 0, 1, swapAmount, 0, 0, false);
|
||||
(uint256 amountInCustom, uint256 amountOutCustom, uint256 feeCustom) = poolCustom.swap(alice, Funding.APPROVALS, alice, 0, 1, swapAmount, 0, 0, false);
|
||||
(uint256 amountInDefault, uint256 amountOutDefault, uint256 feeDefault) = poolDefault.swap(alice, Funding.APPROVAL, alice, 0, 1, swapAmount, 0, 0, false, '');
|
||||
(uint256 amountInCustom, uint256 amountOutCustom, uint256 feeCustom) = poolCustom.swap(alice, Funding.APPROVAL, alice, 0, 1, swapAmount, 0, 0, false, '');
|
||||
|
||||
// Swap results should be identical
|
||||
assertEq(amountInDefault, amountInCustom, "Swap input amounts should be identical");
|
||||
@@ -1055,29 +1001,11 @@ contract PartyPoolTest is Test {
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
int128 kappaDefault2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, kappaDefault2, feePpm, feePpm, false);
|
||||
int128 kappaCustom2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, kappaCustom2, feePpm, feePpm, false);
|
||||
|
||||
// Mint additional _tokens
|
||||
token0.mint(address(this), INIT_BAL * 4);
|
||||
token1.mint(address(this), INIT_BAL * 4);
|
||||
token2.mint(address(this), INIT_BAL * 4);
|
||||
|
||||
// Transfer identical amounts to both pools
|
||||
token0.transfer(address(poolDefault), INIT_BAL);
|
||||
token1.transfer(address(poolDefault), INIT_BAL);
|
||||
token2.transfer(address(poolDefault), INIT_BAL);
|
||||
|
||||
token0.transfer(address(poolCustom), INIT_BAL);
|
||||
token1.transfer(address(poolCustom), INIT_BAL);
|
||||
token2.transfer(address(poolCustom), INIT_BAL);
|
||||
|
||||
// Initialize pools with different LP amounts
|
||||
uint256 lpDefault = poolDefault.initialMint(address(this), 0);
|
||||
(IPartyPool poolDefault, uint256 lpDefault) = Deploy.newPartyPool2("LP_DEFAULT", "LP_DEFAULT", tokens, kappaDefault2, feePpm, feePpm, false, INIT_BAL, 0);
|
||||
uint256 scaleFactor = 3;
|
||||
uint256 customLpAmount = lpDefault * scaleFactor;
|
||||
poolCustom.initialMint(address(this), customLpAmount);
|
||||
int128 kappaCustom2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
(IPartyPool poolCustom,) = Deploy.newPartyPool2("LP_CUSTOM", "LP_CUSTOM", tokens, kappaCustom2, feePpm, feePpm, false, INIT_BAL, customLpAmount);
|
||||
|
||||
// Verify initial LP supplies
|
||||
assertEq(poolDefault.totalSupply(), lpDefault, "Default pool should have default LP supply");
|
||||
|
||||
Reference in New Issue
Block a user