callback-style funding option
This commit is contained in:
102
test/GasTest.sol
102
test/GasTest.sol
@@ -1,15 +1,25 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
/* solhint-disable erc20-unchecked-transfer */
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
import "../src/PartyPlanner.sol";
|
||||
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
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 {IERC3156FlashBorrower} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.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 {TestERC20, FlashBorrower} from "./GasTest.sol";
|
||||
|
||||
/* solhint-disable erc20-unchecked-transfer */
|
||||
|
||||
/// @notice Test contract that implements the flash callback for testing flash loans
|
||||
contract FlashBorrower is IERC3156FlashBorrower {
|
||||
@@ -99,10 +109,10 @@ contract GasTest is Test {
|
||||
using SafeERC20 for TestERC20;
|
||||
|
||||
PartyPlanner internal planner;
|
||||
PartyPool internal pool2;
|
||||
PartyPool internal pool10;
|
||||
PartyPool internal pool20;
|
||||
PartyPool internal pool50;
|
||||
IPartyPool internal pool2;
|
||||
IPartyPool internal pool10;
|
||||
IPartyPool internal pool20;
|
||||
IPartyPool internal pool50;
|
||||
|
||||
address internal alice;
|
||||
address internal bob;
|
||||
@@ -115,7 +125,7 @@ contract GasTest is Test {
|
||||
uint256 constant internal 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) {
|
||||
function createPool(uint256 numTokens) internal returns (IPartyPool) {
|
||||
// Deploy _tokens dynamically
|
||||
address[] memory tokens = new address[](numTokens);
|
||||
uint256[] memory bases = new uint256[](numTokens);
|
||||
@@ -141,21 +151,21 @@ contract GasTest is Test {
|
||||
}
|
||||
// Compute kappa from slippage params and number of _tokens, then construct pool with kappa
|
||||
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, computedKappa, feePpm, feePpm, false);
|
||||
|
||||
// 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);
|
||||
vm.prank(planner.owner());
|
||||
(IPartyPool newPool, ) = planner.newPool(poolName, poolName, ierc20Tokens, computedKappa, feePpm, feePpm, false,
|
||||
address(this), address(this), initialBalances, 0, 0);
|
||||
|
||||
return newPool;
|
||||
}
|
||||
|
||||
/// @notice Helper to create a pool with the stable-pair optimization enabled
|
||||
function createPoolStable(uint256 numTokens) internal returns (PartyPool) {
|
||||
function createPoolStable(uint256 numTokens) internal returns (IPartyPool) {
|
||||
// Deploy _tokens dynamically
|
||||
address[] memory tokens = new address[](numTokens);
|
||||
uint256[] memory bases = new uint256[](numTokens);
|
||||
@@ -181,7 +191,7 @@ contract GasTest is Test {
|
||||
ierc20Tokens[i] = IERC20(tokens[i]);
|
||||
}
|
||||
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage);
|
||||
PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, computedKappa, feePpm, feePpm, true);
|
||||
IPartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, computedKappa, feePpm, feePpm, true);
|
||||
|
||||
// Transfer initial deposit amounts into pool before initial mint
|
||||
for (uint256 i = 0; i < numTokens; i++) {
|
||||
@@ -227,15 +237,37 @@ contract GasTest is Test {
|
||||
}
|
||||
|
||||
/// @notice Helper function: perform 10 swaps back-and-forth between the first two _tokens.
|
||||
function _performSwapGasTest(PartyPool testPool) internal {
|
||||
function _performSwapGasTest(IPartyPool testPool) internal {
|
||||
_performSwapGasTest(testPool, false);
|
||||
}
|
||||
|
||||
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 _performSwapGasTest(IPartyPool testPool, bool useCallback) internal {
|
||||
IERC20[] memory tokens = testPool.allTokens();
|
||||
require(tokens.length >= 2, "Pool must have at least 2 tokens");
|
||||
address payer;
|
||||
address spender;
|
||||
bytes4 selector;
|
||||
|
||||
// Ensure alice approves pool for both _tokens
|
||||
if (useCallback) {
|
||||
payer = address(this);
|
||||
spender = address(this);
|
||||
selector = this.sendTokensCallback.selector;
|
||||
}
|
||||
else {
|
||||
payer = alice;
|
||||
spender = address(testPool);
|
||||
selector = bytes4(0);
|
||||
}
|
||||
vm.prank(alice);
|
||||
TestERC20(address(tokens[0])).approve(address(testPool), type(uint256).max);
|
||||
TestERC20(address(tokens[0])).approve(spender, type(uint256).max);
|
||||
vm.prank(alice);
|
||||
TestERC20(address(tokens[1])).approve(address(testPool), type(uint256).max);
|
||||
TestERC20(address(tokens[1])).approve(spender, type(uint256).max);
|
||||
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
@@ -244,10 +276,10 @@ contract GasTest is Test {
|
||||
vm.prank(alice);
|
||||
if (i % 2 == 0) {
|
||||
// swap token0 -> token1
|
||||
testPool.swap(alice, alice, 0, 1, maxIn, 0, 0, false);
|
||||
testPool.swap(payer, selector, alice, 0, 1, maxIn, 0, 0, false);
|
||||
} else {
|
||||
// swap token1 -> token0
|
||||
testPool.swap(alice, alice, 1, 0, maxIn, 0, 0, false);
|
||||
testPool.swap(payer, selector, alice, 1, 0, maxIn, 0, 0, false);
|
||||
}
|
||||
// shake up the bits
|
||||
maxIn *= 787;
|
||||
@@ -265,6 +297,12 @@ contract GasTest is Test {
|
||||
_performSwapGasTest(pool10);
|
||||
}
|
||||
|
||||
/// @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, true);
|
||||
}
|
||||
|
||||
|
||||
/// @notice Gas measurement: perform 10 swaps back-and-forth between first two _tokens in the 20-token pool.
|
||||
function testSwapGasTwenty() public {
|
||||
_performSwapGasTest(pool20);
|
||||
@@ -277,24 +315,24 @@ contract GasTest is Test {
|
||||
|
||||
/// @notice Gas measurement: perform 10 swaps back-and-forth on a 2-token stable pair (stable-path enabled)
|
||||
function testSwapGasStablePair() public {
|
||||
PartyPool stablePair = createPoolStable(2);
|
||||
IPartyPool stablePair = createPoolStable(2);
|
||||
_performSwapGasTest(stablePair);
|
||||
}
|
||||
|
||||
/// @notice Gas-style test: alternate swapMint then burnSwap on a 2-token stable pair
|
||||
function testSwapMintBurnSwapGasStablePair() public {
|
||||
PartyPool stablePair = createPoolStable(2);
|
||||
IPartyPool stablePair = createPoolStable(2);
|
||||
_performSwapMintBurnSwapGasTest(stablePair);
|
||||
}
|
||||
|
||||
/// @notice Combined gas test (mint then burn) on 2-token stable pair using mint() and burn().
|
||||
function testMintBurnGasStablePair() public {
|
||||
PartyPool stablePair = createPoolStable(2);
|
||||
IPartyPool stablePair = createPoolStable(2);
|
||||
_performMintBurnGasTest(stablePair);
|
||||
}
|
||||
|
||||
/// @notice Helper function: alternate swapMint then burnSwap to keep pool size roughly stable.
|
||||
function _performSwapMintBurnSwapGasTest(PartyPool testPool) internal {
|
||||
function _performSwapMintBurnSwapGasTest(IPartyPool testPool) internal {
|
||||
uint256 iterations = 10;
|
||||
uint256 input = 1_000;
|
||||
IERC20[] memory tokens = testPool.allTokens();
|
||||
@@ -339,7 +377,7 @@ contract GasTest is Test {
|
||||
|
||||
/// @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 {
|
||||
function _performMintBurnGasTest(IPartyPool testPool) internal {
|
||||
uint256 iterations = 50;
|
||||
uint256 input = 1_000;
|
||||
IERC20[] memory poolTokens = testPool.allTokens();
|
||||
|
||||
@@ -142,6 +142,7 @@ contract NativeTest is Test {
|
||||
// Send native currency with {value: maxIn}
|
||||
(uint256 amountIn, uint256 amountOut, ) = pool.swap{value: maxIn}(
|
||||
alice, // payer
|
||||
bytes4(0),
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
@@ -179,6 +180,7 @@ contract NativeTest is Test {
|
||||
// Execute swap: token0 (index 0) -> WETH (index 2) with unwrap=true
|
||||
(uint256 amountIn, uint256 amountOut, ) = pool.swap(
|
||||
alice, // payer
|
||||
bytes4(0), // no selector: use ERC20 approvals
|
||||
alice, // receiver
|
||||
0, // inputTokenIndex (token0)
|
||||
2, // outputTokenIndex (WETH)
|
||||
@@ -214,6 +216,7 @@ contract NativeTest is Test {
|
||||
// Execute swap with excess native currency
|
||||
(uint256 amountIn, , ) = pool.swap{value: totalSent}(
|
||||
alice, // payer
|
||||
bytes4(0),
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
@@ -542,14 +545,14 @@ contract NativeTest is Test {
|
||||
// 2. Swap native currency for token0
|
||||
uint256 swapAmount = 5_000;
|
||||
(, uint256 amountOut, ) = pool.swap{value: swapAmount}(
|
||||
alice, alice, 2, 0, swapAmount, 0, 0, false
|
||||
alice,bytes4(0),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, alice, 0, 2, token0Balance / 2, 0, 0, true
|
||||
alice, bytes4(0), alice, 0, 2, token0Balance / 2, 0, 0, true
|
||||
);
|
||||
assertTrue(swapOut2 > 0, "Should receive native currency");
|
||||
|
||||
@@ -576,7 +579,7 @@ contract NativeTest is Test {
|
||||
|
||||
// Swap token0 -> WETH without unwrap
|
||||
(, uint256 amountOut, ) = pool.swap(
|
||||
alice, alice, 0, 2, maxIn, 0, 0, false // unwrap=false
|
||||
alice, bytes4(0), alice, 0, 2, maxIn, 0, 0, false // unwrap=false
|
||||
);
|
||||
|
||||
assertTrue(amountOut > 0, "Should receive WETH tokens");
|
||||
@@ -597,7 +600,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, alice, 0, 1, 10_000, 0, 0, false
|
||||
alice, bytes4(0), alice, 0, 1, 10_000, 0, 0, false
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
@@ -423,7 +423,7 @@ contract PartyPoolTest is Test {
|
||||
|
||||
// Execute swap: token0 -> token1
|
||||
vm.prank(alice);
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swap(alice, bob, 0, 1, maxIn, 0, 0, false);
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swap(alice, bytes4(0), bob, 0, 1, maxIn, 0, 0, false);
|
||||
|
||||
// Amounts should be positive and not exceed provided max
|
||||
assertTrue(amountInUsed > 0, "expected some input used");
|
||||
@@ -452,7 +452,7 @@ contract PartyPoolTest is Test {
|
||||
|
||||
vm.prank(alice);
|
||||
vm.expectRevert(bytes("LMSR: limitPrice <= current price"));
|
||||
pool.swap(alice, alice, 0, 1, 1000, limitPrice, 0, false);
|
||||
pool.swap(alice, bytes4(0), alice, 0, 1, 1000, limitPrice, 0, false);
|
||||
}
|
||||
|
||||
/// @notice swapToLimit should compute input needed to reach a slightly higher price and execute.
|
||||
@@ -1024,8 +1024,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, alice, 0, 1, swapAmount, 0, 0, false);
|
||||
(uint256 amountInCustom, uint256 amountOutCustom, uint256 feeCustom) = poolCustom.swap(alice, alice, 0, 1, swapAmount, 0, 0, false);
|
||||
(uint256 amountInDefault, uint256 amountOutDefault, uint256 feeDefault) = poolDefault.swap(alice, bytes4(0), alice, 0, 1, swapAmount, 0, 0, false);
|
||||
(uint256 amountInCustom, uint256 amountOutCustom, uint256 feeCustom) = poolCustom.swap(alice, bytes4(0), alice, 0, 1, swapAmount, 0, 0, false);
|
||||
|
||||
// Swap results should be identical
|
||||
assertEq(amountInDefault, amountInCustom, "Swap input amounts should be identical");
|
||||
|
||||
Reference in New Issue
Block a user