diff --git a/script/DeploySepolia.sol b/script/DeploySepolia.sol index 66f5e5e..72f1f21 100644 --- a/script/DeploySepolia.sol +++ b/script/DeploySepolia.sol @@ -35,6 +35,7 @@ contract DeploySepolia is Script { // deploy a PartyPlanner factory and create the pool via factory PartyPlanner planner = new PartyPlanner( + msg.sender, // admin address is the same as the deployer WETH, swapImpl, mintImpl, diff --git a/src/IOwnable.sol b/src/IOwnable.sol new file mode 100644 index 0000000..f948df1 --- /dev/null +++ b/src/IOwnable.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) + +pragma solidity ^0.8.20; + +/** + * @dev OpenZeppelin's Ownable contract, split into internal and external parts. + */ +interface IOwnable { + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + function owner() external view returns (address); + function renounceOwnership() external; + function transferOwnership(address newOwner) external; +} diff --git a/src/IPartyPlanner.sol b/src/IPartyPlanner.sol index 8bf6d08..19d2365 100644 --- a/src/IPartyPlanner.sol +++ b/src/IPartyPlanner.sol @@ -8,38 +8,38 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title IPartyPlanner /// @notice Interface for factory contract for creating and tracking PartyPool instances -interface IPartyPlanner { +interface IPartyPlanner is IOwnable { // Event emitted when a new pool is created event PartyStarted(IPartyPool indexed pool, string name, string symbol, IERC20[] tokens); /// @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 - /// @param symbol_ LP token symbol - /// @param _tokens token addresses (n) - /// @param _bases scaling _bases for each token (n) - used when converting to/from internal 64.64 amounts - /// @param _tradeFrac trade fraction in 64.64 fixed-point (as used by LMSR) - /// @param _targetSlippage target slippage in 64.64 fixed-point (as used by LMSR) - /// @param _swapFeePpm fee 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 name LP token name + /// @param symbol LP token symbol + /// @param tokens token addresses (n) + /// @param bases scaling bases for each token (n) - used when converting to/from internal 64.64 amounts + /// @param tradeFrac trade fraction in 64.64 fixed-point (as used by LMSR) + /// @param targetSlippage target slippage in 64.64 fixed-point (as used by LMSR) + /// @param swapFeePpm fee 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 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 + /// @return lpAmount Amount of LP tokens minted to the receiver function newPool( // Pool constructor args (legacy) - string memory name_, - string memory symbol_, - IERC20[] memory _tokens, - uint256[] memory _bases, - int128 _tradeFrac, - int128 _targetSlippage, - uint256 _swapFeePpm, - uint256 _flashFeePpm, - bool _stable, + string memory name, + string memory symbol, + IERC20[] memory tokens, + uint256[] memory bases, + int128 tradeFrac, + int128 targetSlippage, + uint256 swapFeePpm, + uint256 flashFeePpm, + bool stable, // Initial deposit information address payer, address receiver, @@ -49,30 +49,30 @@ interface IPartyPlanner { ) external returns (IPartyPool pool, uint256 lpAmount); /// @notice Creates a new PartyPool instance and initializes it with initial deposits (kappa-based). - /// @param name_ LP token name - /// @param symbol_ LP token symbol - /// @param _tokens token addresses (n) - /// @param _bases scaling _bases for each token (n) - used when converting to/from internal 64.64 amounts - /// @param _kappa liquidity parameter κ in 64.64 fixed-point used to derive b = κ * S(q) - /// @param _swapFeePpm fee 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 name LP token name + /// @param symbol LP token symbol + /// @param tokens token addresses (n) + /// @param bases scaling bases for each token (n) - used when converting to/from internal 64.64 amounts + /// @param kappa liquidity parameter κ in 64.64 fixed-point used to derive b = κ * S(q) + /// @param swapFeePpm fee 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 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 + /// @return lpAmount Amount of LP tokens minted to the receiver function newPool( // Pool constructor args (kappa-based) - string memory name_, - string memory symbol_, - IERC20[] memory _tokens, - uint256[] memory _bases, - int128 _kappa, - uint256 _swapFeePpm, - uint256 _flashFeePpm, - bool _stable, + string memory name, + string memory symbol, + IERC20[] memory tokens, + uint256[] memory bases, + int128 kappa, + uint256 swapFeePpm, + uint256 flashFeePpm, + bool stable, // Initial deposit information address payer, address receiver, @@ -96,8 +96,8 @@ interface IPartyPlanner { /// @return pools Array of pool addresses for the requested page function getAllPools(uint256 offset, uint256 limit) external view returns (IPartyPool[] memory pools); - /// @notice Returns the total number of unique _tokens - /// @return The total count of unique _tokens + /// @notice Returns the total number of unique tokens + /// @return The total count of unique tokens function tokenCount() external view returns (uint256); /// @notice Retrieves a page of token addresses diff --git a/src/IPartyPool.sol b/src/IPartyPool.sol index da059d9..4303cc9 100644 --- a/src/IPartyPool.sol +++ b/src/IPartyPool.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.30; import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol"; -import "./NativeWrapper.sol"; +import "./IOwnable.sol"; import "./LMSRStabilized.sol"; +import "./NativeWrapper.sol"; import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; @@ -20,7 +21,7 @@ import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20 /// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf calls. /// The contract uses ceiling/floor rules described in function comments to bias rounding in favor of the pool /// (i.e., floor outputs to users, ceil inputs/fees where appropriate). -interface IPartyPool is IERC20Metadata { +interface IPartyPool is IERC20Metadata, IOwnable { // All int128's are ABDKMath64x64 format // Events @@ -62,10 +63,10 @@ interface IPartyPool is IERC20Metadata { function LMSR() external view returns (LMSRStabilized.State memory); /// @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. + /// @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 - /// @notice Returns the number of _tokens (n) in the pool. + /// @notice Returns the number of tokens (n) in the pool. function numTokens() external view returns (uint256); /// @notice Returns the list of all token addresses in the pool (copy). @@ -75,7 +76,7 @@ interface IPartyPool is IERC20Metadata { function wrapperToken() external view returns (NativeWrapper); /// @notice Per-token uint base denominators used to convert uint token amounts <-> internal Q64.64 representation. - /// @dev denominators()[i] is the base for _tokens[i]. These _bases are chosen by deployer and must match token decimals. + /// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals. function denominators() external view returns (uint256[] memory); /// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations. @@ -88,10 +89,10 @@ interface IPartyPool is IERC20Metadata { /// @dev This is the fraction (in ppm) of the pool-collected fees that are owed to the protocol. function protocolFeePpm() external view returns (uint256); - /// @notice Address that will receive collected protocol _tokens when collectProtocolFees() is called. + /// @notice Address that will receive collected protocol tokens when collectProtocolFees() is called. function protocolFeeAddress() external view returns (address); - /// @notice Protocol fee ledger accessor. Returns _tokens owed (raw uint token units) from this pool as protocol fees + /// @notice Protocol fee ledger accessor. Returns tokens owed (raw uint token units) from this pool as protocol fees /// that have not yet been transferred out. function allProtocolFeesOwed() external view returns (uint256[] memory); @@ -102,31 +103,36 @@ interface IPartyPool is IERC20Metadata { /// @dev Pools are constructed with a κ value; this getter exposes the κ used by the pool. function kappa() external view returns (int128); + /// @notice If a security problem is found, the vault owner may call this function to permanently disable swap and + /// mint functionality, leaving only burns (withdrawals) working. + function kill() external; + function killed() external view returns (bool); + // Initialization / Mint / Burn (LP token managed) /// @notice Initial mint to set up pool for the first time. - /// @dev Assumes _tokens have already been transferred to the pool prior to calling. + /// @dev Assumes tokens have already been transferred to the pool prior to calling. /// Can only be called when the pool is uninitialized (totalSupply() == 0 or _lmsr.nAssets == 0). - /// @param receiver address that receives the LP _tokens - /// @param lpTokens The number of LP _tokens to issue for this mint. If 0, then the number of _tokens returned will equal the LMSR internal q total + /// @param receiver address that receives the LP tokens + /// @param lpTokens The number of LP tokens to issue for this mint. If 0, then the number of tokens returned will equal the LMSR internal q total function initialMint(address receiver, uint256 lpTokens) external payable returns (uint256 lpMinted); /// @notice Proportional mint (or initial supply if first call). - /// @dev - For initial supply: assumes _tokens have already been transferred to the pool prior to calling. + /// @dev - For initial supply: assumes tokens have already been transferred to the pool prior to calling. /// - For subsequent mints: payer must approve the required token amounts before calling. /// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs). - /// @param payer address that provides the input _tokens (ignored for initial deposit) - /// @param receiver address that receives the LP _tokens - /// @param lpTokenAmount desired amount of LP _tokens to mint (ignored for initial deposit) + /// @param payer address that provides the input tokens (ignored for initial deposit) + /// @param receiver address that receives the LP tokens + /// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit) /// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore. /// @return lpMinted the actual amount of lpToken minted function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external payable returns (uint256 lpMinted); - /// @notice Burn LP _tokens and withdraw the proportional basket to receiver. + /// @notice Burn LP tokens and withdraw the proportional basket to receiver. /// @dev This function forwards the call to the burn implementation via delegatecall - /// @param payer address that provides the LP _tokens to burn - /// @param receiver address that receives the withdrawn _tokens - /// @param lpAmount amount of LP _tokens to burn (proportional withdrawal) + /// @param payer address that provides the LP tokens to burn + /// @param receiver address that receives the withdrawn tokens + /// @param lpAmount amount of LP tokens to burn (proportional withdrawal) /// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore. /// @param unwrap if true and the native token is being withdrawn, it is unwraped and sent as native currency function burn(address payer, address receiver, uint256 lpAmount, uint256 deadline, bool unwrap) external returns (uint256[] memory withdrawAmounts); @@ -149,9 +155,9 @@ interface IPartyPool is IERC20Metadata { /// @notice Swap input token inputTokenIndex -> token outputTokenIndex. Payer must approve token inputTokenIndex. /// @dev This function transfers the exact gross input (including fee) from payer and sends the computed output to receiver. - /// Non-standard _tokens (fee-on-transfer, rebasers) are rejected via balance checks. + /// Non-standard tokens (fee-on-transfer, rebasers) are rejected via balance checks. /// @param payer address of the account that pays for the swap - /// @param receiver address that will receive the output _tokens + /// @param receiver address that will receive the output tokens /// @param inputTokenIndex index of input asset /// @param outputTokenIndex index of output asset /// @param maxAmountIn maximum amount of token inputTokenIndex (uint256) to transfer in (inclusive of fees) @@ -173,7 +179,7 @@ interface IPartyPool is IERC20Metadata { /// @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. /// @param payer address of the account that pays for the swap - /// @param receiver address that will receive the output _tokens + /// @param receiver address that will receive the output tokens /// @param inputTokenIndex index of input asset /// @param outputTokenIndex index of output asset /// @param limitPrice target marginal price to reach (must be > 0) @@ -193,7 +199,7 @@ interface IPartyPool is IERC20Metadata { /// @dev swapMint executes as an exact-in planned swap followed by proportional scaling of qInternal. /// The function emits SwapMint (gross, net, fee) and also emits Mint for LP issuance. /// @param payer who transfers the input token - /// @param receiver who receives the minted LP _tokens + /// @param receiver who receives the minted LP tokens /// @param inputTokenIndex index of the input token /// @param maxAmountIn maximum uint token input (inclusive of fee) /// @param deadline optional deadline @@ -206,11 +212,11 @@ interface IPartyPool is IERC20Metadata { uint256 deadline ) external payable returns (uint256 lpMinted); - /// @notice Burn LP _tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. - /// @dev The function burns LP _tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state. - /// @param payer who burns LP _tokens + /// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. + /// @dev The function burns LP tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state. + /// @param payer who burns LP tokens /// @param receiver who receives the single asset - /// @param lpAmount amount of LP _tokens to burn + /// @param lpAmount amount of LP tokens to burn /// @param inputTokenIndex index of target asset to receive /// @param deadline optional deadline /// @return amountOutUint uint amount of asset inputTokenIndex sent to receiver @@ -225,9 +231,9 @@ interface IPartyPool is IERC20Metadata { /** * @dev Initiate a flash loan. - * @param receiver The receiver of the _tokens in the loan, and the receiver of the callback. + * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. * @param token The loan currency. - * @param amount The amount of _tokens lent. + * @param amount The amount of tokens lent. * @param data Arbitrary data structure, intended to contain user-defined parameters. */ function flashLoan( diff --git a/src/OwnableExternal.sol b/src/OwnableExternal.sol new file mode 100644 index 0000000..089abc6 --- /dev/null +++ b/src/OwnableExternal.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) + +pragma solidity ^0.8.20; + +import "../lib/openzeppelin-contracts/contracts/utils/Context.sol"; +import {OwnableInternal} from "./OwnableInternal.sol"; +import {IOwnable} from "./IOwnable.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * The initial owner is set to the address provided by the deployer. This can + * later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract OwnableExternal is OwnableInternal, IOwnable { + /** + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. + */ + constructor(address initialOwner) { + if (initialOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(initialOwner); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() external view virtual returns (address) { + return _owner; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() external virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) external virtual onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + +} diff --git a/src/OwnableInternal.sol b/src/OwnableInternal.sol new file mode 100644 index 0000000..1bb80b8 --- /dev/null +++ b/src/OwnableInternal.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) + +pragma solidity ^0.8.20; + +import {Context} from "../lib/openzeppelin-contracts/contracts/utils/Context.sol"; +import {IOwnable} from "./IOwnable.sol"; + +/** + * @dev OpenZeppelin's Ownable contract, split into internal and external parts. + */ +abstract contract OwnableInternal is Context { + address internal _owner; + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (_owner != _msgSender()) { + revert IOwnable.OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit IOwnable.OwnershipTransferred(oldOwner, newOwner); + } +} diff --git a/src/PartyPlanner.sol b/src/PartyPlanner.sol index 901aea3..5f95102 100644 --- a/src/PartyPlanner.sol +++ b/src/PartyPlanner.sol @@ -5,15 +5,17 @@ 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 {NativeWrapper} from "./NativeWrapper.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 {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol"; import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol"; /// @title PartyPlanner /// @notice Factory contract for creating and tracking PartyPool instances -contract PartyPlanner is IPartyPlanner { +contract PartyPlanner is OwnableExternal, IPartyPlanner { using SafeERC20 for IERC20; int128 private constant ONE = int128(1) << 64; @@ -46,32 +48,37 @@ contract PartyPlanner is IPartyPlanner { mapping(IERC20 => bool) private _tokenSupported; mapping(IERC20 => IPartyPool[]) private _poolsByToken; - /// @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 _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)) + /// @param owner_ Initial administrator who is allowed to create new pools and kill() old ones + /// @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 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( - NativeWrapper _wrapper, - PartyPoolSwapImpl _swapImpl, - PartyPoolMintImpl _mintImpl, - IPartyPoolDeployer _deployer, - IPartyPoolDeployer _balancedPairDeployer, - uint256 _protocolFeePpm, - address _protocolFeeAddress - ) { - 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; + address owner_, + NativeWrapper wrapper_, + PartyPoolSwapImpl swapImpl_, + PartyPoolMintImpl mintImpl_, + IPartyPoolDeployer deployer_, + IPartyPoolDeployer balancedPairDeployer_, + uint256 protocolFeePpm_, + address protocolFeeAddress_ + ) + OwnableExternal(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; - PROTOCOL_FEE_ADDRESS = _protocolFeeAddress; + require(protocolFeePpm_ < 1_000_000, "Planner: protocol fee >= ppm"); + PROTOCOL_FEE_PPM = protocolFeePpm_; + PROTOCOL_FEE_ADDRESS = protocolFeeAddress_; } /// Main newPool variant: accepts kappa directly (preferred). @@ -79,38 +86,39 @@ contract PartyPlanner is IPartyPlanner { // Pool constructor args string memory name_, string memory symbol_, - IERC20[] memory _tokens, - uint256[] memory _bases, - int128 _kappa, - uint256 _swapFeePpm, - uint256 _flashFeePpm, - bool _stable, + IERC20[] memory tokens_, + uint256[] memory bases_, + int128 kappa_, + uint256 swapFeePpm_, + uint256 flashFeePpm_, + bool stable_, // Initial deposit information address payer, address receiver, uint256[] memory initialDeposits, uint256 initialLpAmount, uint256 deadline - ) public returns (IPartyPool pool, uint256 lpAmount) { + ) public onlyOwner returns (IPartyPool pool, uint256 lpAmount) { // Validate inputs require(deadline == 0 || block.timestamp <= deadline, "Planner: deadline exceeded"); - require(_tokens.length == initialDeposits.length, "Planner: tokens and deposits length mismatch"); + require(tokens_.length == initialDeposits.length, "Planner: tokens and deposits length mismatch"); require(payer != address(0), "Planner: payer cannot be zero address"); require(receiver != address(0), "Planner: receiver cannot be zero address"); // Validate kappa > 0 (Q64.64) - require(_kappa > int128(0), "Planner: kappa must be > 0"); + require(kappa_ > int128(0), "Planner: kappa must be > 0"); // Create a new PartyPool instance (kappa-based constructor) - IPartyPoolDeployer deployer = _stable && _tokens.length == 2 ? BALANCED_PAIR_DEPLOYER : NORMAL_POOL_DEPLOYER; + IPartyPoolDeployer deployer = stable_ && tokens_.length == 2 ? BALANCED_PAIR_DEPLOYER : NORMAL_POOL_DEPLOYER; pool = deployer.deploy( + _owner, // Same owner as this PartyPlanner name_, symbol_, - _tokens, - _bases, - _kappa, - _swapFeePpm, - _flashFeePpm, + tokens_, + bases_, + kappa_, + swapFeePpm_, + flashFeePpm_, PROTOCOL_FEE_PPM, PROTOCOL_FEE_ADDRESS, WRAPPER, @@ -122,8 +130,8 @@ contract PartyPlanner is IPartyPlanner { _poolSupported[pool] = true; // Track _tokens and populate mappings - for (uint256 i = 0; i < _tokens.length; i++) { - IERC20 token = _tokens[i]; + for (uint256 i = 0; i < tokens_.length; i++) { + IERC20 token = tokens_[i]; // Add token to _allTokens if not already present if (!_tokenSupported[token]) { @@ -135,17 +143,17 @@ contract PartyPlanner is IPartyPlanner { _poolsByToken[token].push(pool); } - emit PartyStarted(pool, name_, symbol_, _tokens); + emit PartyStarted(pool, name_, symbol_, tokens_); // Transfer initial _tokens from payer to the pool - for (uint256 i = 0; i < _tokens.length; i++) { + for (uint256 i = 0; i < tokens_.length; i++) { if (initialDeposits[i] > 0) { - IERC20(_tokens[i]).safeTransferFrom(payer, address(pool), initialDeposits[i]); - require(IERC20(_tokens[i]).balanceOf(address(pool)) == initialDeposits[i], 'fee-on-transfer tokens not supported'); + IERC20(tokens_[i]).safeTransferFrom(payer, address(pool), initialDeposits[i]); + require(IERC20(tokens_[i]).balanceOf(address(pool)) == initialDeposits[i], 'fee-on-transfer tokens not supported'); } } - // Call mint on the new pool to initialize it with the transferred _tokens + // Call mint on the new pool to initialize it with the transferred tokens_ lpAmount = pool.initialMint(receiver, initialLpAmount); } @@ -156,37 +164,37 @@ contract PartyPlanner is IPartyPlanner { // Pool constructor args (old signature) string memory name_, string memory symbol_, - IERC20[] memory _tokens, - uint256[] memory _bases, - int128 _tradeFrac, - int128 _targetSlippage, - uint256 _swapFeePpm, - uint256 _flashFeePpm, - bool _stable, + IERC20[] memory tokens_, + uint256[] memory bases_, + int128 tradeFrac_, + int128 targetSlippage_, + uint256 swapFeePpm_, + uint256 flashFeePpm_, + bool stable_, // Initial deposit information address payer, address receiver, uint256[] memory initialDeposits, uint256 initialLpAmount, uint256 deadline - ) external returns (IPartyPool pool, uint256 lpAmount) { + ) external onlyOwner returns (IPartyPool pool, uint256 lpAmount) { // Validate fixed-point fractions: must be less than 1.0 in 64.64 fixed-point - require(_tradeFrac < ONE, "Planner: tradeFrac must be < 1 (64.64)"); - require(_targetSlippage < ONE, "Planner: targetSlippage must be < 1 (64.64)"); + require(tradeFrac_ < ONE, "Planner: tradeFrac must be < 1 (64.64)"); + require(targetSlippage_ < ONE, "Planner: targetSlippage must be < 1 (64.64)"); // Compute kappa from slippage params using LMSR helper (kappa depends only on n, f and s) - int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(_tokens.length, _tradeFrac, _targetSlippage); + int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(tokens_.length, tradeFrac_, targetSlippage_); // Delegate to the kappa-based newPool variant return newPool( name_, symbol_, - _tokens, - _bases, + tokens_, + bases_, computedKappa, - _swapFeePpm, - _flashFeePpm, - _stable, + swapFeePpm_, + flashFeePpm_, + stable_, payer, receiver, initialDeposits, diff --git a/src/PartyPool.sol b/src/PartyPool.sol index b9f6a34..007afc3 100644 --- a/src/PartyPool.sol +++ b/src/PartyPool.sol @@ -18,27 +18,33 @@ import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/Ree import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC3156FlashLender} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol"; import {NativeWrapper} from "./NativeWrapper.sol"; +import {OwnableExternal} from "./OwnableExternal.sol"; /// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token /// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model. /// The pool issues an ERC20 LP token representing proportional ownership. /// It supports: -/// - Proportional minting and burning of LP _tokens, -/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap), +/// - Proportional minting and burning of LP tokens, /// - Exact-input swaps and swaps-to-price-limits, -/// - Flash loans via a callback interface. +/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap), +/// - ERC-3156 flash loans /// -/// @dev The contract stores per-token uint "_bases" used to scale token units into the internal Q64.64 -/// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf calls. +/// @dev The contract stores per-token uint `_bases` used to scale token units into the internal Q64.64 +/// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf() calls. /// The contract uses ceiling/floor rules described in function comments to bias rounding in favor of the pool -/// (i.e., floor outputs to users, ceil inputs/fees where appropriate). -contract PartyPool is PartyPoolBase, ERC20External, IPartyPool { +/// (i.e., floor outputs to users, ceil inputs/fees where appropriate). Mutating methods have re-entrancy locks. +/// The contract may be "killed" by the admin in case any security issue is discovered, in which case all swaps and +/// mints are disabled, and only the burn() method remains functional to allow LP's to withdraw their assets. +contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool { using ABDKMath64x64 for int128; using LMSRStabilized for LMSRStabilized.State; using SafeERC20 for IERC20; receive() external payable {} + /// @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; } /// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q) @@ -90,6 +96,7 @@ contract PartyPool is PartyPoolBase, ERC20External, 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) @@ -100,6 +107,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool { /// @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_, @@ -114,8 +122,10 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool { PartyPoolMintImpl mintImpl_ ) PartyPoolBase(wrapperToken_) + OwnableExternal(owner_) ERC20External(name_, symbol_) { + require(owner_ != address(0)); require(tokens_.length > 1, "Pool: need >1 asset"); require(tokens_.length == bases_.length, "Pool: lengths mismatch"); _tokens = tokens_; @@ -149,6 +159,11 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool { _protocolFeesOwed = new uint256[](n); } + /// @notice If a security problem is found, the vault owner may call this function to permanently disable swap and + /// mint functionality, leaving only burns (withdrawals) working. + function kill() external onlyOwner { + _killed = true; + } /* ---------------------- Initialization / Mint / Burn (LP token managed) @@ -225,7 +240,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool { int128 limitPrice, uint256 deadline, bool unwrap - ) external payable native nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) { + ) external payable native nonReentrant killable returns (uint256 amountIn, uint256 amountOut, uint256 fee) { require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded"); // Compute amounts using the same path as views @@ -425,7 +440,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool { address tokenAddr, uint256 amount, bytes calldata data - ) external nonReentrant returns (bool) + ) external nonReentrant killable returns (bool) { IERC20 token = IERC20(tokenAddr); require(amount <= token.balanceOf(address(this))); diff --git a/src/PartyPoolBalancedPair.sol b/src/PartyPoolBalancedPair.sol index 44255dd..80f04ce 100644 --- a/src/PartyPoolBalancedPair.sol +++ b/src/PartyPoolBalancedPair.sol @@ -11,6 +11,7 @@ import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol"; contract PartyPoolBalancedPair is PartyPool { constructor( + address owner_, string memory name_, string memory symbol_, IERC20[] memory tokens_, @@ -23,7 +24,8 @@ contract PartyPoolBalancedPair is PartyPool { NativeWrapper wrapperToken_, PartyPoolSwapImpl swapMintImpl_, PartyPoolMintImpl mintImpl_ - ) PartyPool(name_, symbol_, tokens_, bases_, kappa_, swapFeePpm_, flashFeePpm_, protocolFeePpm_, protocolFeeAddress_, wrapperToken_, swapMintImpl_, mintImpl_) + ) + PartyPool(owner_, name_, symbol_, tokens_, bases_, kappa_, swapFeePpm_, flashFeePpm_, protocolFeePpm_, protocolFeeAddress_, wrapperToken_, swapMintImpl_, mintImpl_) {} function _swapAmountsForExactInput(uint256 i, uint256 j, int128 a, int128 limitPrice) internal virtual override view diff --git a/src/PartyPoolBase.sol b/src/PartyPoolBase.sol index 59e5330..3b0522c 100644 --- a/src/PartyPoolBase.sol +++ b/src/PartyPoolBase.sol @@ -9,10 +9,11 @@ import {LMSRStabilized} from "./LMSRStabilized.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"; +import {OwnableInternal} from "./OwnableInternal.sol"; /// @notice Abstract base contract that contains storage and internal helpers only. -/// No external/public functions or constructor here — derived implementations own immutables and constructors. -abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard, PartyPoolHelpers { +/// No external/public functions here. +abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGuard, PartyPoolHelpers { using ABDKMath64x64 for int128; using LMSRStabilized for LMSRStabilized.State; using SafeERC20 for IERC20; @@ -23,18 +24,13 @@ abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard, PartyPoolHelp WRAPPER_TOKEN = wrapper_; } - /// @notice Designates methods that can receive native currency. - /// @dev If the pool has any balance of native currency at the end of the method, it is refunded to msg.sender - modifier native() { - _; - uint256 bal = address(this).balance; - if(bal > 0) - payable(msg.sender).transfer(bal); - } + // + // Internal state + // - // - // Internal state (no immutables here; immutables belong to derived contracts) - // + /// @notice If _killed is set, then all `killable` methods are permanently disabled, leaving only burns + /// (withdrawals) working + bool internal _killed; // LMSR internal state LMSRStabilized.State internal _lmsr; @@ -64,6 +60,20 @@ abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard, PartyPoolHelp uint256[] internal _cachedUintBalances; + /// @notice Designates methods that can receive native currency. + /// @dev If the pool has any balance of native currency at the end of the method, it is refunded to msg.sender + modifier native() { + _; + uint256 bal = address(this).balance; + if(bal > 0) + payable(msg.sender).transfer(bal); + } + + modifier killable() { + require(!_killed, 'killed'); + _; + } + /* ---------------------- Conversion & fee helpers (internal) ---------------------- */ diff --git a/src/PartyPoolDeployer.sol b/src/PartyPoolDeployer.sol index 929d341..ac297e1 100644 --- a/src/PartyPoolDeployer.sol +++ b/src/PartyPoolDeployer.sol @@ -11,6 +11,7 @@ import {PartyPoolBalancedPair} from "./PartyPoolBalancedPair.sol"; interface IPartyPoolDeployer { function deploy( + address owner_, string memory name_, string memory symbol_, IERC20[] memory tokens_, @@ -28,6 +29,7 @@ interface IPartyPoolDeployer { contract PartyPoolDeployer is IPartyPoolDeployer { function deploy( + address owner_, string memory name_, string memory symbol_, IERC20[] memory tokens_, @@ -42,6 +44,7 @@ contract PartyPoolDeployer is IPartyPoolDeployer { PartyPoolMintImpl mintImpl_ ) external returns (IPartyPool) { return new PartyPool( + owner_, name_, symbol_, tokens_, @@ -60,6 +63,7 @@ contract PartyPoolDeployer is IPartyPoolDeployer { contract PartyPoolBalancedPairDeployer is IPartyPoolDeployer { function deploy( + address owner_, string memory name_, string memory symbol_, IERC20[] memory tokens_, @@ -74,6 +78,7 @@ contract PartyPoolBalancedPairDeployer is IPartyPoolDeployer { PartyPoolMintImpl mintImpl_ ) external returns (IPartyPool) { return new PartyPoolBalancedPair( + owner_, name_, symbol_, tokens_, diff --git a/src/PartyPoolMintImpl.sol b/src/PartyPoolMintImpl.sol index 92929b2..49ff74c 100644 --- a/src/PartyPoolMintImpl.sol +++ b/src/PartyPoolMintImpl.sol @@ -25,7 +25,7 @@ contract PartyPoolMintImpl is PartyPoolBase { // Initialization Mint // - function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external payable native nonReentrant + function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external payable native killable nonReentrant returns (uint256 lpMinted) { uint256 n = _tokens.length; @@ -65,7 +65,7 @@ contract PartyPoolMintImpl is PartyPoolBase { // Regular Mint and Burn // - function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external payable native nonReentrant + function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external payable native killable nonReentrant returns (uint256 lpMinted) { require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded"); uint256 n = _tokens.length; @@ -131,7 +131,8 @@ contract PartyPoolMintImpl is PartyPoolBase { return actualLpToMint; } - /// @notice Burn LP _tokens and withdraw the proportional basket to receiver. + /// @notice Burn LP _tokens and withdraw the proportional basket to receiver. Functional even if the pool has been + /// killed. /// @dev Payer must own or approve the LP _tokens being burned. The function updates LMSR state /// proportionally to reflect the reduced pool size after the withdrawal. /// @param payer address that provides the LP _tokens to burn @@ -345,7 +346,7 @@ contract PartyPoolMintImpl is PartyPoolBase { uint256 deadline, uint256 swapFeePpm, uint256 protocolFeePpm - ) external payable native nonReentrant returns (uint256 lpMinted) { + ) external payable native killable nonReentrant returns (uint256 lpMinted) { uint256 n = _tokens.length; require(inputTokenIndex < n, "swapMint: idx"); require(maxAmountIn > 0, "swapMint: input zero"); @@ -468,6 +469,8 @@ contract PartyPoolMintImpl is PartyPoolBase { } /// @notice Burn LP _tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. + /// This version of burn does not work if the vault has been killed, because it involves a swap. Use regular burn() + /// to recover funds if the pool has been killed. /// @dev The function burns LP _tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state. /// @param payer who burns LP _tokens /// @param receiver who receives the single asset @@ -485,7 +488,7 @@ contract PartyPoolMintImpl is PartyPoolBase { bool unwrap, uint256 swapFeePpm, uint256 protocolFeePpm - ) external nonReentrant returns (uint256 amountOutUint) { + ) external nonReentrant killable returns (uint256 amountOutUint) { uint256 n = _tokens.length; require(inputTokenIndex < n, "burnSwap: idx"); require(lpAmount > 0, "burnSwap: zero lp"); diff --git a/src/PartyPoolSwapImpl.sol b/src/PartyPoolSwapImpl.sol index 3d871a6..fa92f62 100644 --- a/src/PartyPoolSwapImpl.sol +++ b/src/PartyPoolSwapImpl.sol @@ -59,7 +59,7 @@ contract PartyPoolSwapImpl is PartyPoolBase { bool unwrap, uint256 swapFeePpm, uint256 protocolFeePpm - ) external payable native returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) { + ) external payable native killable nonReentrant returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) { uint256 n = _tokens.length; require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx"); require(limitPrice > int128(0), "swapToLimit: limit <= 0"); diff --git a/test/Deploy.sol b/test/Deploy.sol index 4c748b3..4ceece1 100644 --- a/test/Deploy.sol +++ b/test/Deploy.sol @@ -18,11 +18,17 @@ library Deploy { function newPartyPlanner() internal returns (PartyPlanner) { NativeWrapper wrapper = new WETH9(); - return newPartyPlanner(wrapper); + return newPartyPlanner(msg.sender, wrapper); } - function newPartyPlanner(NativeWrapper wrapper) internal returns (PartyPlanner) { + function newPartyPlanner(address owner) internal returns (PartyPlanner) { + NativeWrapper wrapper = new WETH9(); + return newPartyPlanner(owner, wrapper); + } + + function newPartyPlanner(address owner, NativeWrapper wrapper) internal returns (PartyPlanner) { return new PartyPlanner( + owner, wrapper, new PartyPoolSwapImpl(wrapper), new PartyPoolMintImpl(wrapper), @@ -34,6 +40,7 @@ library Deploy { } function newPartyPool( + address owner_, string memory name_, string memory symbol_, IERC20[] memory tokens_, @@ -44,10 +51,11 @@ library Deploy { bool _stable ) internal returns (PartyPool) { NativeWrapper wrapper = new WETH9(); - return newPartyPool(name_, symbol_, tokens_, bases_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable); + return newPartyPool(owner_, name_, symbol_, tokens_, bases_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable); } function newPartyPool( + address owner_, string memory name_, string memory symbol_, IERC20[] memory tokens_, @@ -60,6 +68,7 @@ library Deploy { ) internal returns (PartyPool) { return _stable && tokens_.length == 2 ? new PartyPoolBalancedPair( + owner_, name_, symbol_, tokens_, @@ -74,6 +83,7 @@ library Deploy { new PartyPoolMintImpl(wrapper) ) : new PartyPool( + owner_, name_, symbol_, tokens_, diff --git a/test/GasTest.sol b/test/GasTest.sol index 4e72117..a21bfef 100644 --- a/test/GasTest.sol +++ b/test/GasTest.sol @@ -141,7 +141,7 @@ contract GasTest is Test { } // Compute kappa from slippage params and number of _tokens, then construct pool with kappa int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage); - PartyPool newPool = Deploy.newPartyPool(poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, false); + PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, false); // Transfer initial deposit amounts into pool before initial mint for (uint256 i = 0; i < numTokens; i++) { @@ -181,7 +181,7 @@ contract GasTest is Test { ierc20Tokens[i] = IERC20(tokens[i]); } int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage); - PartyPool newPool = Deploy.newPartyPool(poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, true); + PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, true); // Transfer initial deposit amounts into pool before initial mint for (uint256 i = 0; i < numTokens; i++) { diff --git a/test/NativeTest.t.sol b/test/NativeTest.t.sol index 8be3e3b..5d691e9 100644 --- a/test/NativeTest.t.sol +++ b/test/NativeTest.t.sol @@ -94,7 +94,7 @@ contract NativeTest is Test { uint256 feePpm = 1000; int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); - pool = Deploy.newPartyPool("LP", "LP", tokens, bases, kappa, feePpm, feePpm, weth, false); + pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, bases, kappa, feePpm, feePpm, weth, false); // Transfer initial deposit amounts into pool token0.transfer(address(pool), INIT_BAL); diff --git a/test/PartyPlanner.t.sol b/test/PartyPlanner.t.sol index 66bbfea..d901450 100644 --- a/test/PartyPlanner.t.sol +++ b/test/PartyPlanner.t.sol @@ -46,8 +46,8 @@ contract PartyPlannerTest is Test { uint256 constant INITIAL_DEPOSIT_AMOUNT = 1000e18; function setUp() public { - // Deploy PartyPlanner - planner = Deploy.newPartyPlanner(); + // Deploy PartyPlanner owned by this test contract + planner = Deploy.newPartyPlanner(address(this)); // Deploy mock _tokens tokenA = new MockERC20("Token A", "TKNA", 18); diff --git a/test/PartyPool.t.sol b/test/PartyPool.t.sol index f19ddab..628928a 100644 --- a/test/PartyPool.t.sol +++ b/test/PartyPool.t.sol @@ -173,7 +173,7 @@ contract PartyPoolTest is Test { uint256 feePpm = 1000; int128 kappa3 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); - pool = Deploy.newPartyPool("LP", "LP", tokens, bases, kappa3, feePpm, feePpm, false); + pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, bases, 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 @@ -203,7 +203,7 @@ contract PartyPoolTest is Test { } int128 kappa10 = LMSRStabilized.computeKappaFromSlippage(tokens10.length, tradeFrac, targetSlippage); - pool10 = Deploy.newPartyPool("LP10", "LP10", tokens10, bases10, kappa10, feePpm, feePpm, false); + pool10 = Deploy.newPartyPool(address(this), "LP10", "LP10", tokens10, bases10, kappa10, feePpm, feePpm, false); // Mint additional _tokens for pool10 initial deposit token0.mint(address(this), INIT_BAL); @@ -992,11 +992,11 @@ contract PartyPoolTest is Test { // Pool with default initialization (lpTokens = 0) int128 kappaDefault = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); - PartyPool poolDefault = Deploy.newPartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault, feePpm, feePpm, false); + PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault, feePpm, feePpm, false); // Pool with custom initialization (lpTokens = custom amount) int128 kappaCustom = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); - PartyPool poolCustom = Deploy.newPartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom, feePpm, feePpm, false); + PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom, feePpm, feePpm, false); // Mint additional _tokens for both pools token0.mint(address(this), INIT_BAL * 2); @@ -1068,9 +1068,9 @@ contract PartyPoolTest is Test { uint256 feePpm = 1000; int128 kappaDefault2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); - PartyPool poolDefault = Deploy.newPartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault2, feePpm, feePpm, false); + PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault2, feePpm, feePpm, false); int128 kappaCustom2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); - PartyPool poolCustom = Deploy.newPartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom2, feePpm, feePpm, false); + PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom2, feePpm, feePpm, false); // Mint additional _tokens token0.mint(address(this), INIT_BAL * 4);