CREATE2 callback validation; init code storage contracts
This commit is contained in:
@@ -3,7 +3,13 @@
|
||||
report() {
|
||||
local name=${2:-$1}
|
||||
REPORT=$(forge test --mc GasTest --mt "$1" --gas-report)
|
||||
SWAP=$(echo "$REPORT" | grep 'swap ' | cut -d '|' -f 5 | xargs)
|
||||
SWAP=$(echo "$REPORT" | grep 'swapApproval ' | cut -d '|' -f 5 | xargs)
|
||||
if [ -z "$SWAP" ]; then
|
||||
SWAP=$(echo "$REPORT" | grep 'swapPrefund ' | cut -d '|' -f 5 | xargs)
|
||||
fi
|
||||
if [ -z "$SWAP" ]; then
|
||||
SWAP=$(echo "$REPORT" | grep 'swapCallback ' | cut -d '|' -f 5 | xargs)
|
||||
fi
|
||||
MINT=$(echo "$REPORT" | grep 'mint ' | cut -d '|' -f 5 | xargs)
|
||||
SWAPMINT=$(echo "$REPORT" | grep 'swapMint ' | cut -d '|' -f 5 | xargs)
|
||||
printf "%-15s %10s %10s %10s\n" "$name" "$SWAP" "$MINT" "$SWAPMINT"
|
||||
@@ -15,8 +21,11 @@ printf "%s\n" " ------ --------- ---------"
|
||||
|
||||
report GasPair Pair
|
||||
report StablePair "Stable Pair"
|
||||
report PrefundingSP "SPair prefund"
|
||||
report Ten
|
||||
report Callback "Ten-callback"
|
||||
report Prefunding "Ten-prefunding"
|
||||
report Prefunding10 "Ten prefund"
|
||||
report Callback10 "Ten callback"
|
||||
report Twenty
|
||||
report Prefunding20 "Twenty prefund"
|
||||
report Callback20 "Twenty callback"
|
||||
report Fifty
|
||||
|
||||
@@ -10,7 +10,7 @@ optimizer=true
|
||||
optimizer_runs=100000000 # maximum value allowed by etherscan's verifier XD. The max value is formally 2^32-1
|
||||
viaIR=true
|
||||
#gas_reports = ['PartyPool', 'PartyPoolBalancedPair', 'PartyPlanner', 'PartyPoolSwapImpl', 'PartyPoolMintImpl',]
|
||||
gas_reports = ['PartyPool', 'PartyPoolBalancedPair', 'PartyPlanner']
|
||||
gas_reports = ['PartyPool', 'PartyPoolBalancedPair', 'PartyPlanner', 'GasHarness']
|
||||
fs_permissions = [{ access = "write", path = "liqp-deployments.json"}]
|
||||
|
||||
[lint]
|
||||
|
||||
@@ -10,10 +10,17 @@ import {StdCheatsSafe} from "../lib/forge-std/src/StdCheats.sol";
|
||||
import {StdUtils} from "../lib/forge-std/src/StdUtils.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {IPartyInfo} from "../src/IPartyInfo.sol";
|
||||
import {IPartyPlanner} from "../src/IPartyPlanner.sol";
|
||||
import {NativeWrapper} from "../src/NativeWrapper.sol";
|
||||
import {PartyInfo} from "../src/PartyInfo.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {PartyPoolInitCode, PartyPoolBalancedPairInitCode} from "../src/PartyPoolDeployer.sol";
|
||||
import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol";
|
||||
import {Deploy} from "../test/Deploy.sol";
|
||||
import {MockERC20} from "../test/MockERC20.sol";
|
||||
import {WETH9} from "../test/WETH9.sol";
|
||||
|
||||
contract DeployMock is Script {
|
||||
|
||||
@@ -34,7 +41,19 @@ contract DeployMock is Script {
|
||||
wteth = new MockERC20('Wrapped TETH', 'WTETH', 18);
|
||||
|
||||
// deploy a PartyPlanner factory and create the pool via factory
|
||||
PartyPlanner planner = Deploy.newPartyPlanner();
|
||||
NativeWrapper wrapper = new WETH9();
|
||||
PartyPoolSwapImpl swapImpl = new PartyPoolSwapImpl(wrapper);
|
||||
PartyPoolMintImpl mintImpl = new PartyPoolMintImpl(wrapper);
|
||||
IPartyPlanner planner = new PartyPlanner(
|
||||
msg.sender,
|
||||
wrapper,
|
||||
swapImpl,
|
||||
new PartyPoolMintImpl(wrapper),
|
||||
new PartyPoolInitCode(),
|
||||
new PartyPoolBalancedPairInitCode(),
|
||||
Deploy.PROTOCOL_FEE_PPM,
|
||||
Deploy.PROTOCOL_FEE_RECEIVER
|
||||
);
|
||||
|
||||
//
|
||||
// Deploy 3-asset pool
|
||||
@@ -163,7 +182,7 @@ contract DeployMock is Script {
|
||||
0
|
||||
);
|
||||
|
||||
PartyInfo info = Deploy.newInfo();
|
||||
IPartyInfo info = new PartyInfo(swapImpl, mintImpl);
|
||||
|
||||
// give _tokens to dev7 for later use
|
||||
mintAll(DEV_ACCOUNT_7, 1_000_000);
|
||||
|
||||
@@ -10,15 +10,15 @@ import {StdCheatsSafe} from "../lib/forge-std/src/StdCheats.sol";
|
||||
import {StdUtils} from "../lib/forge-std/src/StdUtils.sol";
|
||||
import {IERC3156FlashBorrower} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {IPartyInfo} from "../src/IPartyInfo.sol";
|
||||
import {IPartyPool} from "../src/IPartyPool.sol";
|
||||
import {LMSRStabilized} from "../src/LMSRStabilized.sol";
|
||||
import {NativeWrapper} from "../src/NativeWrapper.sol";
|
||||
import {PartyInfo} from "../src/PartyInfo.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.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 {MockERC20} from "../test/MockERC20.sol";
|
||||
import {MockFlashBorrower} from "../test/MockFlashBorrower.sol";
|
||||
|
||||
@@ -55,8 +55,8 @@ contract DeploySepolia is Script {
|
||||
|
||||
PartyPoolSwapImpl swapImpl = new PartyPoolSwapImpl(WETH);
|
||||
PartyPoolMintImpl mintImpl = new PartyPoolMintImpl(WETH);
|
||||
PartyPoolDeployer deployer = new PartyPoolDeployer();
|
||||
PartyPoolBalancedPairDeployer balancedPairDeployer = new PartyPoolBalancedPairDeployer();
|
||||
PartyPoolInitCode poolInit = new PartyPoolInitCode();
|
||||
PartyPoolBalancedPairInitCode bpInit = new PartyPoolBalancedPairInitCode();
|
||||
|
||||
// deploy a PartyPlanner factory and create the pool via factory
|
||||
PartyPlanner planner = new PartyPlanner(
|
||||
@@ -64,8 +64,8 @@ contract DeploySepolia is Script {
|
||||
WETH,
|
||||
swapImpl,
|
||||
mintImpl,
|
||||
deployer,
|
||||
balancedPairDeployer,
|
||||
poolInit,
|
||||
bpInit,
|
||||
PROTOCOL_FEE_PPM,
|
||||
PROTOCOL_FEE_ADDRESS
|
||||
);
|
||||
@@ -211,8 +211,8 @@ contract DeploySepolia is Script {
|
||||
console2.log(' PartyInfo', address(info));
|
||||
console2.log(' SwapImpl', address(swapImpl));
|
||||
console2.log(' MintImpl', address(mintImpl));
|
||||
console2.log(' Deployer', address(deployer));
|
||||
console2.log('BPair Deployer', address(balancedPairDeployer));
|
||||
console2.log(' PoolCode', address(poolInit));
|
||||
console2.log(' BPPoolCode', address(bpInit));
|
||||
console2.log();
|
||||
console2.log(' USXD', address(usxd));
|
||||
console2.log(' FUSD', address(fusd));
|
||||
@@ -284,7 +284,7 @@ contract DeploySepolia is Script {
|
||||
uint256 inputIndex = 0;
|
||||
uint256 outputIndex = n > 1 ? n - 1 : 0;
|
||||
uint256 maxIn = 89 * 10**6; // varied
|
||||
pool.swap(msg.sender, bytes4(0), msg.sender, inputIndex, outputIndex, maxIn, int128(0), 0, false);
|
||||
pool.swap(msg.sender, bytes4(0), msg.sender, inputIndex, outputIndex, maxIn, int128(0), 0, false, '');
|
||||
|
||||
// 6) Collect protocol fees now (after some swaps) so some will have been moved out
|
||||
pool.collectProtocolFees();
|
||||
|
||||
16
script/InitCodeHashes.sol
Normal file
16
script/InitCodeHashes.sol
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "forge-std/console2.sol";
|
||||
import {Script} from "../lib/forge-std/src/Script.sol";
|
||||
import {PartyPool} from "../src/PartyPool.sol";
|
||||
import {PartyPoolBalancedPair} from "../src/PartyPoolBalancedPair.sol";
|
||||
|
||||
contract InitCodeHashes is Script {
|
||||
function run() public pure {
|
||||
console2.log('\nPool Init Code Hash');
|
||||
console2.logBytes32(keccak256(type(PartyPool).creationCode));
|
||||
console2.log('\nBP Pool Init Code Hash');
|
||||
console2.logBytes32(keccak256(type(PartyPoolBalancedPair).creationCode));
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ contract ERC20External is ERC20Internal, IERC20Metadata {
|
||||
*
|
||||
* Both values are immutable: they can only be set once during construction.
|
||||
*/
|
||||
constructor(string memory name_, string memory symbol_) {
|
||||
function erc20Constructor(string memory name_, string memory symbol_) internal {
|
||||
_name = name_;
|
||||
_symbol = symbol_;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ abstract contract ERC20Internal is Context, IERC20Errors {
|
||||
string internal _symbol;
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* @dev Moves a `value` amount of _tokens from `from` to `to`.
|
||||
*
|
||||
* This internal function is equivalent to {transfer}, and can be used to
|
||||
|
||||
@@ -4,7 +4,7 @@ pragma solidity ^0.8.30;
|
||||
|
||||
library Funding {
|
||||
/// @notice a constant passed to swap as the fundingSelector to indicate that the payer has used regular ERC20 approvals to allow the pool to move the necessary input tokens.
|
||||
bytes4 internal constant APPROVALS = 0x00000000;
|
||||
bytes4 internal constant APPROVAL = 0x00000000;
|
||||
|
||||
/// @notice a constant passed to swap as the fundingSelector to indicate that the payer has already sent sufficient input tokens to the pool before calling swap, so no movement of input tokens is required.
|
||||
bytes4 internal constant PREFUNDING = 0x00000001;
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
import "./IPartyPool.sol";
|
||||
import "./PartyPoolMintImpl.sol";
|
||||
import "./PartyPoolSwapImpl.sol";
|
||||
import {IOwnable} from "./IOwnable.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
/// @title IPartyPlanner
|
||||
/// @notice Interface for factory contract for creating and tracking PartyPool instances
|
||||
@@ -13,6 +13,38 @@ interface IPartyPlanner is IOwnable {
|
||||
// Event emitted when a new pool is created
|
||||
event PartyStarted(IPartyPool indexed pool, string name, string symbol, IERC20[] tokens);
|
||||
|
||||
|
||||
/// @notice Primary method for creating a new pool. May only be called by the PartyPlanner owner account.
|
||||
/// @param name LP token name
|
||||
/// @param symbol LP token symbol
|
||||
/// @param tokens token addresses
|
||||
/// @param kappa liquidity parameter κ in 64.64 fixed-point used to derive b = κ * S(q)
|
||||
/// @param swapFeesPpm per-asset fees in parts-per-million, taken from swap input amounts before LMSR calculations
|
||||
/// @param flashFeePpm fee in parts-per-million, taken for flash loans
|
||||
/// @param stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated
|
||||
/// @param payer address that provides the initial token deposits
|
||||
/// @param receiver address that receives the minted LP tokens
|
||||
/// @param initialDeposits amounts of each token to deposit initially
|
||||
/// @param deadline Reverts if nonzero and the current blocktime is later than the deadline
|
||||
/// @return pool Address of the newly created and initialized PartyPool
|
||||
/// @return lpAmount Amount of LP tokens minted to the receiver
|
||||
function newPool(
|
||||
// Pool constructor args
|
||||
string memory name,
|
||||
string memory symbol,
|
||||
IERC20[] memory tokens,
|
||||
int128 kappa,
|
||||
uint256[] memory swapFeesPpm,
|
||||
uint256 flashFeePpm,
|
||||
bool stable,
|
||||
// Initial deposit information
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256[] memory initialDeposits,
|
||||
uint256 initialLpAmount,
|
||||
uint256 deadline
|
||||
) external returns (IPartyPool pool, uint256 lpAmount);
|
||||
|
||||
/// @notice Creates a new PartyPool instance and initializes it with initial deposits (legacy signature).
|
||||
/// @dev Deprecated in favour of the kappa-based overload below; kept for backwards compatibility.
|
||||
/// @param name LP token name
|
||||
|
||||
@@ -82,7 +82,7 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
|
||||
/// @notice Token addresses comprising the pool. Effectively immutable after construction.
|
||||
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
|
||||
function getToken(uint256) external view returns (IERC20); // get single token
|
||||
function token(uint256) external view returns (IERC20); // get single token
|
||||
|
||||
/// @notice Returns the number of tokens (n) in the pool.
|
||||
function numTokens() external view returns (uint256);
|
||||
@@ -185,6 +185,8 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
/// @param maxAmountIn maximum amount of token inputTokenIndex (uint256) to transfer in (inclusive of fees)
|
||||
/// @param limitPrice maximum acceptable marginal price (64.64 fixed point). Pass 0 to ignore.
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
/// @param unwrap If true, then any output of wrapper token will be unwrapped and native ETH sent to the receiver.
|
||||
/// @param cbData callback data if fundingSelector is of the callback type.
|
||||
/// @return amountIn actual input used (uint256), amountOut actual output sent (uint256), inFee fee taken from the input (uint256)
|
||||
function swap(
|
||||
address payer,
|
||||
@@ -195,9 +197,11 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable returns (uint256 amountIn, uint256 amountOut, uint256 inFee);
|
||||
|
||||
|
||||
/// @notice Swap up to the price limit; computes max input to reach limit then performs swap.
|
||||
/// @dev If balances prevent fully reaching the limit, the function caps and returns actuals.
|
||||
/// The payer must transfer the exact gross input computed by the view.
|
||||
@@ -210,12 +214,14 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
/// @return amountInUsed actual input used excluding fee (uint256), amountOut actual output sent (uint256), inFee fee taken from the input (uint256)
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
bytes4 fundingSelector,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable returns (uint256 amountInUsed, uint256 amountOut, uint256 inFee);
|
||||
|
||||
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
||||
|
||||
45
src/IPartyPoolDeployer.sol
Normal file
45
src/IPartyPoolDeployer.sol
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {NativeWrapper} from "./NativeWrapper.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
interface IPartyPoolDeployer {
|
||||
|
||||
/// @notice Parameters for deploying a new PartyPool
|
||||
struct DeployParams {
|
||||
/// @notice Used for callback validation
|
||||
bytes32 nonce;
|
||||
/// @notice Admin account that can disable the vault using kill()
|
||||
address owner;
|
||||
/// @notice LP token name
|
||||
string name;
|
||||
/// @notice LP token symbol
|
||||
string symbol;
|
||||
/// @notice Token addresses (n)
|
||||
IERC20[] tokens;
|
||||
/// @notice Liquidity parameter κ (Q64.64) used to derive b = κ * S(q)
|
||||
int128 kappa;
|
||||
/// @notice Per-asset swap fees in ppm (length must equal tokens.length)
|
||||
uint256[] fees;
|
||||
/// @notice Fee in parts-per-million, taken for flash loans
|
||||
uint256 flashFeePpm;
|
||||
/// @notice Protocol fee in parts-per-million
|
||||
uint256 protocolFeePpm;
|
||||
/// @notice Address to receive protocol fees
|
||||
address protocolFeeAddress;
|
||||
/// @notice Native token wrapper contract
|
||||
NativeWrapper wrapper;
|
||||
/// @notice Address of the SwapMint implementation contract
|
||||
PartyPoolSwapImpl swapImpl;
|
||||
/// @notice Address of the Mint implementation contract
|
||||
PartyPoolMintImpl mintImpl;
|
||||
}
|
||||
|
||||
function params() external view returns (DeployParams memory);
|
||||
}
|
||||
|
||||
|
||||
10
src/IPartySwapCallback.sol
Normal file
10
src/IPartySwapCallback.sol
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
interface IPartySwapCallback {
|
||||
// The callback may have any function name. Pass your callback function selector to the swap method as the fundingSelector
|
||||
function liquidityPartySwapCallback(bytes32 nonce, IERC20 inputToken, uint256 amount, bytes memory data) external;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ abstract contract OwnableExternal is OwnableInternal, IOwnable {
|
||||
/**
|
||||
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
|
||||
*/
|
||||
constructor(address initialOwner) {
|
||||
function ownableConstructor(address initialOwner) internal {
|
||||
if (initialOwner == address(0)) {
|
||||
revert OwnableInvalidOwner(address(0));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ contract PartyInfo is PartyPoolHelpers, IPartyInfo {
|
||||
uint256 nAssets = lmsr.qInternal.length;
|
||||
uint256[] memory cachedUintBalances = new uint256[](nAssets);
|
||||
for( uint256 i=0; i<nAssets; i++ )
|
||||
cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool));
|
||||
cachedUintBalances[i] = pool.token(i).balanceOf(address(pool));
|
||||
return MINT_IMPL.mintAmounts(lpTokenAmount, pool.totalSupply(), cachedUintBalances);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ contract PartyInfo is PartyPoolHelpers, IPartyInfo {
|
||||
uint256 nAssets = lmsr.qInternal.length;
|
||||
uint256[] memory cachedUintBalances = new uint256[](nAssets);
|
||||
for( uint256 i=0; i<nAssets; i++ )
|
||||
cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool));
|
||||
cachedUintBalances[i] = pool.token(i).balanceOf(address(pool));
|
||||
return MINT_IMPL.burnAmounts(lpTokenAmount, pool.totalSupply(), cachedUintBalances);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,19 @@ import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20
|
||||
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {IPartyPlanner} from "./IPartyPlanner.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {IPartyPoolDeployer} from "./IPartyPoolDeployer.sol";
|
||||
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||
import {NativeWrapper} from "./NativeWrapper.sol";
|
||||
import {OwnableExternal} from "./OwnableExternal.sol";
|
||||
import {OwnableInternal} from "./OwnableInternal.sol";
|
||||
import {IPartyPoolDeployer} from "./PartyPoolDeployer.sol";
|
||||
import {PartyPoolDeployer, PartyPoolInitCode, PartyPoolBalancedPairInitCode} from "./PartyPoolDeployer.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
/// @title PartyPlanner
|
||||
/// @notice Factory contract for creating and tracking PartyPool instances
|
||||
contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
/// @dev Inherits from PartyPoolDeployer to handle pool deployment directly
|
||||
contract PartyPlanner is PartyPoolDeployer, OwnableExternal, IPartyPlanner {
|
||||
using SafeERC20 for IERC20;
|
||||
int128 private constant ONE = int128(1) << 64;
|
||||
|
||||
@@ -38,9 +40,6 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
NativeWrapper private immutable WRAPPER;
|
||||
function wrapper() external view returns (NativeWrapper) { return WRAPPER; }
|
||||
|
||||
IPartyPoolDeployer private immutable NORMAL_POOL_DEPLOYER;
|
||||
IPartyPoolDeployer private immutable BALANCED_PAIR_DEPLOYER;
|
||||
|
||||
// On-chain pool indexing
|
||||
IPartyPool[] private _allPools;
|
||||
IERC20[] private _allTokens;
|
||||
@@ -52,6 +51,8 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
/// @param wrapper_ The WETH9 implementation address used for this chain
|
||||
/// @param swapImpl_ address of the Swap implementation contract to be used by all pools
|
||||
/// @param mintImpl_ address of the Mint implementation contract to be used by all pools
|
||||
/// @param poolInitCodeStorage_ address of the storage contract holding PartyPool init code
|
||||
/// @param balancedPairInitCodeStorage_ address of the storage contract holding PartyPoolBalancedPair init code
|
||||
/// @param protocolFeePpm_ protocol fee share (ppm) to be used for pools created by this planner
|
||||
/// @param protocolFeeAddress_ recipient address for protocol fees for pools created by this planner (may be address(0))
|
||||
constructor(
|
||||
@@ -59,22 +60,19 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_,
|
||||
IPartyPoolDeployer deployer_,
|
||||
IPartyPoolDeployer balancedPairDeployer_,
|
||||
PartyPoolInitCode poolInitCodeStorage_,
|
||||
PartyPoolBalancedPairInitCode balancedPairInitCodeStorage_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_
|
||||
)
|
||||
OwnableExternal(owner_)
|
||||
PartyPoolDeployer(poolInitCodeStorage_, balancedPairInitCodeStorage_)
|
||||
{
|
||||
ownableConstructor(owner_);
|
||||
WRAPPER = wrapper_;
|
||||
require(address(swapImpl_) != address(0), "Planner: swapImpl address cannot be zero");
|
||||
SWAP_IMPL = swapImpl_;
|
||||
require(address(mintImpl_) != address(0), "Planner: mintImpl address cannot be zero");
|
||||
MINT_IMPL = mintImpl_;
|
||||
require(address(deployer_) != address(0), "Planner: deployer address cannot be zero");
|
||||
NORMAL_POOL_DEPLOYER = deployer_;
|
||||
require(address(balancedPairDeployer_) != address(0), "Planner: balanced pair deployer address cannot be zero");
|
||||
BALANCED_PAIR_DEPLOYER = balancedPairDeployer_;
|
||||
|
||||
require(protocolFeePpm_ < 1_000_000, "Planner: protocol fee >= ppm");
|
||||
PROTOCOL_FEE_PPM = protocolFeePpm_;
|
||||
@@ -111,8 +109,8 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
require(swapFeesPpm_.length == tokens_.length, "Planner: fees and tokens length mismatch");
|
||||
|
||||
// Create a new PartyPool instance (kappa-based constructor)
|
||||
IPartyPoolDeployer deployer = stable_ && tokens_.length == 2 ? BALANCED_PAIR_DEPLOYER : NORMAL_POOL_DEPLOYER;
|
||||
pool = deployer.deploy(
|
||||
IPartyPoolDeployer.DeployParams memory params = IPartyPoolDeployer.DeployParams(
|
||||
0, // This is set by the deployer
|
||||
_owner, // Same owner as this PartyPlanner
|
||||
name_,
|
||||
symbol_,
|
||||
@@ -127,6 +125,13 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
MINT_IMPL
|
||||
);
|
||||
|
||||
// Use inherited deploy methods based on pool type
|
||||
if (stable_ && tokens_.length == 2) {
|
||||
pool = _deployBalancedPair(params);
|
||||
} else {
|
||||
pool = _deploy(params);
|
||||
}
|
||||
|
||||
_allPools.push(pool);
|
||||
_poolSupported[pool] = true;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {OwnableInternal} from "./OwnableInternal.sol";
|
||||
import {PartyPoolBase} from "./PartyPoolBase.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
import {IPartyPoolDeployer} from "./IPartyPoolDeployer.sol";
|
||||
|
||||
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
||||
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
||||
@@ -43,7 +44,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @notice If true, the vault has been disabled by the owner and only burns (withdrawals) are allowed.
|
||||
function killed() external view returns (bool) { return _killed; }
|
||||
|
||||
function wrapperToken() external view returns (NativeWrapper) { return WRAPPER_TOKEN; }
|
||||
function wrapperToken() external view returns (NativeWrapper) { return WRAPPER; }
|
||||
|
||||
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
||||
/// @dev Pool is constructed with a fixed κ. Clients that previously passed tradeFrac/targetSlippage
|
||||
@@ -80,7 +81,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
function swapMintImpl() external view returns (PartyPoolSwapImpl) { return SWAP_IMPL; }
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function getToken(uint256 i) external view returns (IERC20) { return _tokens[i]; }
|
||||
function token(uint256 i) external view returns (IERC20) { return _tokens[i]; }
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function numTokens() external view returns (uint256) { return _tokens.length; }
|
||||
@@ -94,61 +95,42 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @inheritdoc IPartyPool
|
||||
function LMSR() external view returns (LMSRStabilized.State memory) { return _lmsr; }
|
||||
|
||||
|
||||
/// @param owner_ Admin account that can disable the vault using kill()
|
||||
/// @param name_ LP token name
|
||||
/// @param symbol_ LP token symbol
|
||||
/// @param tokens_ token addresses (n)
|
||||
/// @param kappa_ liquidity parameter κ (Q64.64) used to derive b = κ * S(q)
|
||||
/// @param fees_ per-asset swap fees in ppm (length must equal tokens_.length)
|
||||
/// @param flashFeePpm_ fee in parts-per-million, taken for flash loans
|
||||
/// @param swapImpl_ address of the SwapMint implementation contract
|
||||
/// @param mintImpl_ address of the Mint implementation contract
|
||||
constructor(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapperToken_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
)
|
||||
PartyPoolBase(wrapperToken_)
|
||||
OwnableExternal(owner_)
|
||||
ERC20External(name_, symbol_)
|
||||
constructor()
|
||||
{
|
||||
require(owner_ != address(0));
|
||||
require(tokens_.length > 1, "Pool: need >1 asset");
|
||||
_tokens = tokens_;
|
||||
KAPPA = kappa_;
|
||||
require(fees_.length == tokens_.length, "Pool: fees length");
|
||||
// validate ppm bounds and assign
|
||||
_fees = new uint256[](fees_.length);
|
||||
for (uint256 i = 0; i < fees_.length; i++) {
|
||||
// Cap all fees at 1%
|
||||
require(fees_[i] < 10_000, "Pool: fee >= 1%");
|
||||
_fees[i] = fees_[i];
|
||||
}
|
||||
require(flashFeePpm_ < 10_000, "Pool: flash fee >= 1%");
|
||||
FLASH_FEE_PPM = flashFeePpm_;
|
||||
require(protocolFeePpm_ < 400_000, "Pool: protocol fee >= 40%");
|
||||
// If the protocolFeePpm_ is set, then also require the fee address to be nonzero
|
||||
require(protocolFeePpm_ == 0 || protocolFeeAddress_ != address(0));
|
||||
PROTOCOL_FEE_PPM = protocolFeePpm_;
|
||||
protocolFeeAddress = protocolFeeAddress_;
|
||||
SWAP_IMPL = swapImpl_;
|
||||
MINT_IMPL = mintImpl_;
|
||||
IPartyPoolDeployer.DeployParams memory p = IPartyPoolDeployer(msg.sender).params();
|
||||
uint256 n = p.tokens.length;
|
||||
require(n > 1, "Pool: need >1 asset");
|
||||
|
||||
uint256 n = tokens_.length;
|
||||
_nonce = p.nonce;
|
||||
WRAPPER = p.wrapper;
|
||||
_name = p.name;
|
||||
_symbol = p.symbol;
|
||||
|
||||
ownableConstructor(p.owner);
|
||||
|
||||
_tokens = p.tokens;
|
||||
KAPPA = p.kappa;
|
||||
require(p.fees.length == p.tokens.length, "Pool: fees length");
|
||||
// validate ppm bounds and assign
|
||||
_fees = new uint256[](p.fees.length);
|
||||
for (uint256 i = 0; i < p.fees.length; i++) {
|
||||
// Cap all fees at 1%
|
||||
require(p.fees[i] < 10_000, "Pool: fee >= 1%");
|
||||
_fees[i] = p.fees[i];
|
||||
}
|
||||
require(p.flashFeePpm < 10_000, "Pool: flash fee >= 1%");
|
||||
FLASH_FEE_PPM = p.flashFeePpm;
|
||||
require(p.protocolFeePpm < 400_000, "Pool: protocol fee >= 40%");
|
||||
// If the p.protocolFeePpm is set, then also require the fee address to be nonzero
|
||||
require(p.protocolFeePpm == 0 || p.protocolFeeAddress != address(0));
|
||||
PROTOCOL_FEE_PPM = p.protocolFeePpm;
|
||||
protocolFeeAddress = p.protocolFeeAddress;
|
||||
SWAP_IMPL = p.swapImpl;
|
||||
MINT_IMPL = p.mintImpl;
|
||||
|
||||
// Initialize token address to index mapping
|
||||
for (uint i = 0; i < n;) {
|
||||
_tokenAddressToIndexPlusOne[tokens_[i]] = i + 1;
|
||||
_tokenAddressToIndexPlusOne[p.tokens[i]] = i + 1;
|
||||
unchecked {i++;}
|
||||
}
|
||||
|
||||
@@ -252,7 +234,8 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable native nonReentrant killable returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
|
||||
|
||||
@@ -264,24 +247,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
IERC20 tokenIn = _tokens[inputTokenIndex];
|
||||
IERC20 tokenOut = _tokens[outputTokenIndex];
|
||||
|
||||
if (fundingSelector == Funding.APPROVALS)
|
||||
// Regular ERC20 permit of the pool to move the tokens
|
||||
_receiveTokenFrom(payer, tokenIn, totalTransferAmount);
|
||||
else if (fundingSelector == Funding.PREFUNDING) {
|
||||
require(limitPrice==0, 'Prefunding cannot be used with a limit price');
|
||||
uint256 balance = tokenIn.balanceOf(address(this));
|
||||
uint256 prevBalance = _cachedUintBalances[inputTokenIndex] + _protocolFeesOwed[inputTokenIndex];
|
||||
require( balance - prevBalance == totalTransferAmount, 'Incorrect prefunding amount');
|
||||
}
|
||||
else {
|
||||
// Callback-style funding mechanism
|
||||
uint256 startingBalance = tokenIn.balanceOf(address(this));
|
||||
bytes memory data = abi.encodeWithSelector(fundingSelector, tokenIn, totalTransferAmount);
|
||||
// Invoke the payer callback; no return value expected (reverts on failure)
|
||||
Address.functionCall(payer, data);
|
||||
uint256 endingBalance = tokenIn.balanceOf(address(this));
|
||||
require(endingBalance-startingBalance == totalTransferAmount, 'Insufficient funds');
|
||||
}
|
||||
_receiveTokenFrom(payer, fundingSelector, inputTokenIndex, tokenIn, totalTransferAmount, limitPrice, cbData);
|
||||
|
||||
// Compute on-chain balances as: onchain = cached + owed (+/- transfer)
|
||||
uint256 balIAfter = _cachedUintBalances[inputTokenIndex] + _protocolFeesOwed[inputTokenIndex] + totalTransferAmount;
|
||||
@@ -369,22 +335,26 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @inheritdoc IPartyPool
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
bytes4 fundingSelector,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable returns (uint256 amountInUsed, uint256 amountOut, uint256 inFee) {
|
||||
bytes memory data = abi.encodeWithSelector(
|
||||
PartyPoolSwapImpl.swapToLimit.selector,
|
||||
payer,
|
||||
fundingSelector,
|
||||
receiver,
|
||||
inputTokenIndex,
|
||||
outputTokenIndex,
|
||||
limitPrice,
|
||||
deadline,
|
||||
unwrap,
|
||||
cbData,
|
||||
_pairFeePpm(inputTokenIndex, outputTokenIndex),
|
||||
PROTOCOL_FEE_PPM
|
||||
);
|
||||
|
||||
@@ -10,23 +10,6 @@ import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
contract PartyPoolBalancedPair is PartyPool {
|
||||
constructor(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_, // NEW: protocol share of fees (ppm)
|
||||
address protocolFeeAddress_, // NEW: recipient for collected protocol tokens
|
||||
NativeWrapper wrapperToken_,
|
||||
PartyPoolSwapImpl swapMintImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
)
|
||||
PartyPool(owner_, name_, symbol_, tokens_, kappa_, fees_, flashFeePpm_, protocolFeePpm_, protocolFeeAddress_, wrapperToken_, swapMintImpl_, mintImpl_)
|
||||
{}
|
||||
|
||||
function _swapAmountsForExactInput(uint256 i, uint256 j, int128 a, int128 limitPrice) internal virtual override view
|
||||
returns (int128 amountIn, int128 amountOut) {
|
||||
return LMSRStabilizedBalancedPair.swapAmountsForExactInput(_lmsr, i, j, a, limitPrice);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "../lib/openzeppelin-contracts/contracts/utils/Address.sol";
|
||||
import "./Funding.sol";
|
||||
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.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 {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||
import {ERC20Internal} from "./ERC20Internal.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||
import {NativeWrapper} from "./NativeWrapper.sol";
|
||||
import {OwnableInternal} from "./OwnableInternal.sol";
|
||||
import {PartyPoolHelpers} from "./PartyPoolHelpers.sol";
|
||||
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
/// @notice Abstract base contract that contains storage and internal helpers only.
|
||||
/// No external/public functions here.
|
||||
@@ -18,11 +20,8 @@ abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGua
|
||||
using LMSRStabilized for LMSRStabilized.State;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
NativeWrapper internal immutable WRAPPER_TOKEN;
|
||||
|
||||
constructor( NativeWrapper wrapper_ ) {
|
||||
WRAPPER_TOKEN = wrapper_;
|
||||
}
|
||||
bytes32 internal _nonce; // used for callback validation
|
||||
NativeWrapper internal immutable WRAPPER;
|
||||
|
||||
/// @notice Per-asset swap fees in ppm. Fees are applied on input for swaps; see helpers for composition rules.
|
||||
uint256[] internal _fees;
|
||||
@@ -144,12 +143,41 @@ abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGua
|
||||
Token transfer helpers (includes autowrap)
|
||||
---------------------- */
|
||||
|
||||
function _receiveTokenFrom(address payer, bytes4 fundingSelector, uint256 tokenIndex, IERC20 token, uint256 amount, int128 limitPrice, bytes memory cbData) internal {
|
||||
if (fundingSelector == Funding.APPROVAL) {
|
||||
// Regular ERC20 permit of the pool to move the tokens
|
||||
_receiveTokenFrom(payer, token, amount);
|
||||
}
|
||||
else if (fundingSelector == Funding.PREFUNDING) {
|
||||
// Tokens are already deposited into the pool
|
||||
require(limitPrice==0, 'Prefunding cannot be used with a limit price');
|
||||
if( token == WRAPPER && msg.value >= amount )
|
||||
WRAPPER.deposit{value:amount}();
|
||||
else {
|
||||
uint256 balance = token.balanceOf(address(this));
|
||||
uint256 prevBalance = _cachedUintBalances[tokenIndex] + _protocolFeesOwed[tokenIndex];
|
||||
require( balance - prevBalance == amount, 'Incorrect prefunding amount');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Callback-style funding mechanism
|
||||
// Does not support native transfer.
|
||||
uint256 startingBalance = token.balanceOf(address(this));
|
||||
bytes memory data = abi.encodeWithSelector(fundingSelector, _nonce, token, amount, cbData);
|
||||
// Invoke the payer callback; no return value expected (reverts on failure)
|
||||
Address.functionCall(payer, data);
|
||||
uint256 endingBalance = token.balanceOf(address(this));
|
||||
require(endingBalance-startingBalance == amount, 'Insufficient funds');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @notice Receive _tokens from `payer` into the pool (address(this)) using SafeERC20 semantics.
|
||||
/// @dev Note: this helper does NOT query the on-chain balance after transfer to save gas.
|
||||
/// Callers should query the balance themselves when they need it (e.g., to detect fee-on-transfer _tokens).
|
||||
function _receiveTokenFrom(address payer, IERC20 token, uint256 amount) internal {
|
||||
if( token == WRAPPER_TOKEN && msg.value >= amount )
|
||||
WRAPPER_TOKEN.deposit{value:amount}();
|
||||
if( token == WRAPPER && msg.value >= amount )
|
||||
WRAPPER.deposit{value:amount}();
|
||||
else
|
||||
token.safeTransferFrom(payer, address(this), amount);
|
||||
}
|
||||
@@ -158,8 +186,8 @@ abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGua
|
||||
/// @dev Note: this helper does NOT query the on-chain balance after transfer to save gas.
|
||||
/// Callers should query the balance themselves when they need it (e.g., to detect fee-on-transfer _tokens).
|
||||
function _sendTokenTo(IERC20 token, address receiver, uint256 amount, bool unwrap) internal {
|
||||
if( unwrap && token == WRAPPER_TOKEN ) {
|
||||
WRAPPER_TOKEN.withdraw(amount);
|
||||
if( unwrap && token == WRAPPER) {
|
||||
WRAPPER.withdraw(amount);
|
||||
(bool ok, ) = receiver.call{value: amount}("");
|
||||
require(ok, 'receiver not payable');
|
||||
}
|
||||
|
||||
@@ -1,91 +1,95 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "./PartyPoolMintImpl.sol";
|
||||
import "./PartyPoolSwapImpl.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {IPartyPoolDeployer} from "./IPartyPoolDeployer.sol";
|
||||
import {PartyPool} from "./PartyPool.sol";
|
||||
import {PartyPoolBalancedPair} from "./PartyPoolBalancedPair.sol";
|
||||
|
||||
// This pattern is needed because the PartyPlanner constructs two different types of pools (regular and balanced-pair)
|
||||
// but doesn't have room to store the initialization code of both contracts. Therefore, we delegate pool construction.
|
||||
|
||||
interface IPartyPoolDeployer {
|
||||
function deploy(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
) external returns (IPartyPool pool);
|
||||
// Storage contracts that only hold the init code
|
||||
contract PartyPoolInitCode {
|
||||
constructor() {
|
||||
bytes memory code = type(PartyPool).creationCode;
|
||||
assembly {
|
||||
return(add(code, 0x20), mload(code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract PartyPoolBalancedPairInitCode {
|
||||
constructor() {
|
||||
bytes memory code = type(PartyPoolBalancedPair).creationCode;
|
||||
assembly {
|
||||
return(add(code, 0x20), mload(code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Unified deployer that loads init code from external storage contracts
|
||||
/// @dev This pattern avoids storing large init code in the deployer itself, reducing contract size.
|
||||
/// Holds storage addresses for both regular and balanced pair pools, with separate nonce counters.
|
||||
contract PartyPoolDeployer is IPartyPoolDeployer {
|
||||
function deploy(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
) external returns (IPartyPool) {
|
||||
return new PartyPool(
|
||||
owner_,
|
||||
name_,
|
||||
symbol_,
|
||||
tokens_,
|
||||
kappa_,
|
||||
fees_,
|
||||
flashFeePpm_,
|
||||
protocolFeePpm_,
|
||||
protocolFeeAddress_,
|
||||
wrapper_,
|
||||
swapImpl_,
|
||||
mintImpl_
|
||||
);
|
||||
}
|
||||
}
|
||||
address private immutable POOL_INIT_CODE_STORAGE;
|
||||
address private immutable BALANCED_PAIR_INIT_CODE_STORAGE;
|
||||
|
||||
contract PartyPoolBalancedPairDeployer is IPartyPoolDeployer {
|
||||
function deploy(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
) external returns (IPartyPool) {
|
||||
return new PartyPoolBalancedPair(
|
||||
owner_,
|
||||
name_,
|
||||
symbol_,
|
||||
tokens_,
|
||||
kappa_,
|
||||
fees_,
|
||||
flashFeePpm_,
|
||||
protocolFeePpm_,
|
||||
protocolFeeAddress_,
|
||||
wrapper_,
|
||||
swapImpl_,
|
||||
mintImpl_
|
||||
);
|
||||
uint256 private _poolNonce;
|
||||
uint256 private _balancedPairNonce;
|
||||
DeployParams private _params;
|
||||
|
||||
constructor(PartyPoolInitCode poolInitCodeStorage, PartyPoolBalancedPairInitCode balancedPairInitCodeStorage) {
|
||||
require(address(poolInitCodeStorage) != address(0), "Deployer: zero pool storage address");
|
||||
require(address(balancedPairInitCodeStorage) != address(0), "Deployer: zero balanced pair storage address");
|
||||
POOL_INIT_CODE_STORAGE = address(poolInitCodeStorage);
|
||||
BALANCED_PAIR_INIT_CODE_STORAGE = address(balancedPairInitCodeStorage);
|
||||
}
|
||||
|
||||
function params() external view returns (DeployParams memory) {
|
||||
return _params;
|
||||
}
|
||||
|
||||
/// @notice Deploy a regular PartyPool
|
||||
function _deploy(DeployParams memory params_) internal returns (IPartyPool pool) {
|
||||
return _doDeploy(params_, POOL_INIT_CODE_STORAGE, _poolNonce++);
|
||||
}
|
||||
|
||||
/// @notice Deploy a balanced pair PartyPool
|
||||
function _deployBalancedPair(DeployParams memory params_) internal returns (IPartyPool pool) {
|
||||
return _doDeploy(params_, BALANCED_PAIR_INIT_CODE_STORAGE, _balancedPairNonce++);
|
||||
}
|
||||
|
||||
/// @notice Internal deployment implementation shared by both pool types
|
||||
function _doDeploy(
|
||||
DeployParams memory params_,
|
||||
address initCodeStorage,
|
||||
uint256 nonce
|
||||
) internal returns (IPartyPool pool) {
|
||||
bytes32 salt = bytes32(nonce);
|
||||
_params = params_;
|
||||
_params.nonce = salt;
|
||||
|
||||
// Load init code from storage contract and deploy with CREATE2
|
||||
bytes memory initCode = _getInitCode(initCodeStorage);
|
||||
address poolAddress;
|
||||
assembly {
|
||||
poolAddress := create2(0, add(initCode, 0x20), mload(initCode), salt)
|
||||
if iszero(poolAddress) {
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pool = IPartyPool(poolAddress);
|
||||
}
|
||||
|
||||
/// @notice Load init code from the specified storage contract using EXTCODECOPY
|
||||
function _getInitCode(address storageContract) internal view returns (bytes memory) {
|
||||
uint256 size;
|
||||
assembly {
|
||||
size := extcodesize(storageContract)
|
||||
}
|
||||
bytes memory code = new bytes(size);
|
||||
assembly {
|
||||
extcodecopy(storageContract, add(code, 0x20), 0, size)
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
||||
using LMSRStabilized for LMSRStabilized.State;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
constructor(NativeWrapper wrapper_) PartyPoolBase(wrapper_) {}
|
||||
constructor(NativeWrapper wrapper_) {WRAPPER = wrapper_;}
|
||||
|
||||
//
|
||||
// Initialization Mint
|
||||
|
||||
@@ -18,7 +18,7 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
using LMSRStabilized for LMSRStabilized.State;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
constructor(NativeWrapper wrapper_) PartyPoolBase(wrapper_) {}
|
||||
constructor(NativeWrapper wrapper_) {WRAPPER = wrapper_;}
|
||||
|
||||
bytes32 internal constant FLASH_CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
|
||||
@@ -94,12 +94,14 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
bytes4 fundingSelector,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap,
|
||||
bytes memory cbData,
|
||||
uint256 swapFeePpm,
|
||||
uint256 protocolFeePpm
|
||||
) external payable native killable nonReentrant returns (uint256 amountInUsed, uint256 amountOut, uint256 inFee) {
|
||||
@@ -109,18 +111,15 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
|
||||
|
||||
// Read previous balances for affected assets
|
||||
uint256 prevBalI = IERC20(_tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
uint256 prevBalJ = IERC20(_tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
uint256 prevBalJ = _cachedUintBalances[outputTokenIndex];
|
||||
|
||||
// Compute amounts using the same path as views
|
||||
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
|
||||
_quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice, swapFeePpm);
|
||||
|
||||
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
|
||||
// Transfer the exact amount needed from payer
|
||||
IERC20 tokenIn = _tokens[inputTokenIndex];
|
||||
_receiveTokenFrom(payer, tokenIn, totalTransferAmount);
|
||||
uint256 balIAfter = tokenIn.balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
|
||||
_receiveTokenFrom(payer, fundingSelector, inputTokenIndex, tokenIn, totalTransferAmount, limitPrice, cbData);
|
||||
|
||||
// Transfer output to receiver and verify exact decrease
|
||||
IERC20 tokenOut = _tokens[outputTokenIndex];
|
||||
@@ -137,10 +136,6 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Update caches to effective balances (inline _recordCachedBalance)
|
||||
require(balIAfter >= _protocolFeesOwed[inputTokenIndex], "balance < protocol owed");
|
||||
_cachedUintBalances[inputTokenIndex] = balIAfter - _protocolFeesOwed[inputTokenIndex];
|
||||
|
||||
require(balJAfter >= _protocolFeesOwed[outputTokenIndex], "balance < protocol owed");
|
||||
_cachedUintBalances[outputTokenIndex] = balJAfter - _protocolFeesOwed[outputTokenIndex];
|
||||
|
||||
|
||||
27
src/PartySwapCallbackVerifier.sol
Normal file
27
src/PartySwapCallbackVerifier.sol
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IPartyPlanner} from "./IPartyPlanner.sol";
|
||||
import {PartyPool} from "./PartyPool.sol";
|
||||
import {PartyPoolBalancedPair} from "./PartyPoolBalancedPair.sol";
|
||||
|
||||
library PartySwapCallbackVerifier {
|
||||
|
||||
// To use this verification in your own library, run `forge script InitCodeHashes` and replace the computed hashes below with the hardcoded bytes32 hash
|
||||
function verifyCallback(IPartyPlanner planner, bytes32 nonce) internal view {
|
||||
if(_verify(planner, keccak256(type(PartyPool).creationCode), nonce)) return;
|
||||
if(_verify(planner, keccak256(type(PartyPoolBalancedPair).creationCode), nonce)) return;
|
||||
revert('unauthorized callback');
|
||||
}
|
||||
|
||||
function _verify(IPartyPlanner planner, bytes32 initCodeHash, bytes32 nonce) internal view returns (bool) {
|
||||
address predicted = address(uint160(uint256(keccak256(abi.encodePacked(
|
||||
bytes1(0xff),
|
||||
address(planner),
|
||||
nonce,
|
||||
initCodeHash
|
||||
)))));
|
||||
return predicted == msg.sender;
|
||||
}
|
||||
|
||||
}
|
||||
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