ownership & killable

This commit is contained in:
tim
2025-10-19 13:35:33 -04:00
parent 5aa0032be0
commit d55be28cba
18 changed files with 364 additions and 176 deletions

View File

@@ -35,6 +35,7 @@ contract DeploySepolia is Script {
// deploy a PartyPlanner factory and create the pool via factory // deploy a PartyPlanner factory and create the pool via factory
PartyPlanner planner = new PartyPlanner( PartyPlanner planner = new PartyPlanner(
msg.sender, // admin address is the same as the deployer
WETH, WETH,
swapImpl, swapImpl,
mintImpl, mintImpl,

25
src/IOwnable.sol Normal file
View File

@@ -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;
}

View File

@@ -8,38 +8,38 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @title IPartyPlanner /// @title IPartyPlanner
/// @notice Interface for factory contract for creating and tracking PartyPool instances /// @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 emitted when a new pool is created
event PartyStarted(IPartyPool indexed pool, string name, string symbol, IERC20[] tokens); 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). /// @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. /// @dev Deprecated in favour of the kappa-based overload below; kept for backwards compatibility.
/// @param name_ LP token name /// @param name LP token name
/// @param symbol_ LP token symbol /// @param symbol LP token symbol
/// @param _tokens token addresses (n) /// @param tokens token addresses (n)
/// @param _bases scaling _bases for each token (n) - used when converting to/from internal 64.64 amounts /// @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 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 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 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 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 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 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 initialDeposits amounts of each token to deposit initially
/// @param deadline Reverts if nonzero and the current blocktime is later than the deadline /// @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 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( function newPool(
// Pool constructor args (legacy) // Pool constructor args (legacy)
string memory name_, string memory name,
string memory symbol_, string memory symbol,
IERC20[] memory _tokens, IERC20[] memory tokens,
uint256[] memory _bases, uint256[] memory bases,
int128 _tradeFrac, int128 tradeFrac,
int128 _targetSlippage, int128 targetSlippage,
uint256 _swapFeePpm, uint256 swapFeePpm,
uint256 _flashFeePpm, uint256 flashFeePpm,
bool _stable, bool stable,
// Initial deposit information // Initial deposit information
address payer, address payer,
address receiver, address receiver,
@@ -49,30 +49,30 @@ interface IPartyPlanner {
) external returns (IPartyPool pool, uint256 lpAmount); ) external returns (IPartyPool pool, uint256 lpAmount);
/// @notice Creates a new PartyPool instance and initializes it with initial deposits (kappa-based). /// @notice Creates a new PartyPool instance and initializes it with initial deposits (kappa-based).
/// @param name_ LP token name /// @param name LP token name
/// @param symbol_ LP token symbol /// @param symbol LP token symbol
/// @param _tokens token addresses (n) /// @param tokens token addresses (n)
/// @param _bases scaling _bases for each token (n) - used when converting to/from internal 64.64 amounts /// @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 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 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 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 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 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 initialDeposits amounts of each token to deposit initially
/// @param deadline Reverts if nonzero and the current blocktime is later than the deadline /// @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 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( function newPool(
// Pool constructor args (kappa-based) // Pool constructor args (kappa-based)
string memory name_, string memory name,
string memory symbol_, string memory symbol,
IERC20[] memory _tokens, IERC20[] memory tokens,
uint256[] memory _bases, uint256[] memory bases,
int128 _kappa, int128 kappa,
uint256 _swapFeePpm, uint256 swapFeePpm,
uint256 _flashFeePpm, uint256 flashFeePpm,
bool _stable, bool stable,
// Initial deposit information // Initial deposit information
address payer, address payer,
address receiver, address receiver,
@@ -96,8 +96,8 @@ interface IPartyPlanner {
/// @return pools Array of pool addresses for the requested page /// @return pools Array of pool addresses for the requested page
function getAllPools(uint256 offset, uint256 limit) external view returns (IPartyPool[] memory pools); function getAllPools(uint256 offset, uint256 limit) external view returns (IPartyPool[] memory pools);
/// @notice Returns the total number of unique _tokens /// @notice Returns the total number of unique tokens
/// @return The total count of unique _tokens /// @return The total count of unique tokens
function tokenCount() external view returns (uint256); function tokenCount() external view returns (uint256);
/// @notice Retrieves a page of token addresses /// @notice Retrieves a page of token addresses

View File

@@ -2,8 +2,9 @@
pragma solidity ^0.8.30; pragma solidity ^0.8.30;
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol"; import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
import "./NativeWrapper.sol"; import "./IOwnable.sol";
import "./LMSRStabilized.sol"; import "./LMSRStabilized.sol";
import "./NativeWrapper.sol";
import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.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. /// 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 /// 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). /// (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 // All int128's are ABDKMath64x64 format
// Events // Events
@@ -62,10 +63,10 @@ interface IPartyPool is IERC20Metadata {
function LMSR() external view returns (LMSRStabilized.State memory); function LMSR() external view returns (LMSRStabilized.State memory);
/// @notice Token addresses comprising the pool. Effectively immutable after construction. /// @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 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); function numTokens() external view returns (uint256);
/// @notice Returns the list of all token addresses in the pool (copy). /// @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); function wrapperToken() external view returns (NativeWrapper);
/// @notice Per-token uint base denominators used to convert uint token amounts <-> internal Q64.64 representation. /// @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); 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. /// @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. /// @dev This is the fraction (in ppm) of the pool-collected fees that are owed to the protocol.
function protocolFeePpm() external view returns (uint256); 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); 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. /// that have not yet been transferred out.
function allProtocolFeesOwed() external view returns (uint256[] memory); 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. /// @dev Pools are constructed with a κ value; this getter exposes the κ used by the pool.
function kappa() external view returns (int128); 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) // Initialization / Mint / Burn (LP token managed)
/// @notice Initial mint to set up pool for the first time. /// @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). /// Can only be called when the pool is uninitialized (totalSupply() == 0 or _lmsr.nAssets == 0).
/// @param receiver address that receives the LP _tokens /// @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 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); function initialMint(address receiver, uint256 lpTokens) external payable returns (uint256 lpMinted);
/// @notice Proportional mint (or initial supply if first call). /// @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. /// - 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). /// 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 payer address that provides the input tokens (ignored for initial deposit)
/// @param receiver address that receives the LP _tokens /// @param receiver address that receives the LP tokens
/// @param lpTokenAmount desired amount of LP _tokens to mint (ignored for initial deposit) /// @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. /// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
/// @return lpMinted the actual amount of lpToken minted /// @return lpMinted the actual amount of lpToken minted
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external payable returns (uint256 lpMinted); 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 /// @dev This function forwards the call to the burn implementation via delegatecall
/// @param payer address that provides the LP _tokens to burn /// @param payer address that provides the LP tokens to burn
/// @param receiver address that receives the withdrawn _tokens /// @param receiver address that receives the withdrawn tokens
/// @param lpAmount amount of LP _tokens to burn (proportional withdrawal) /// @param lpAmount amount of LP tokens to burn (proportional withdrawal)
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore. /// @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 /// @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); 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. /// @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. /// @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 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 inputTokenIndex index of input asset
/// @param outputTokenIndex index of output asset /// @param outputTokenIndex index of output asset
/// @param maxAmountIn maximum amount of token inputTokenIndex (uint256) to transfer in (inclusive of fees) /// @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. /// @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. /// The payer must transfer the exact gross input computed by the view.
/// @param payer address of the account that pays for the swap /// @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 inputTokenIndex index of input asset
/// @param outputTokenIndex index of output asset /// @param outputTokenIndex index of output asset
/// @param limitPrice target marginal price to reach (must be > 0) /// @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. /// @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. /// The function emits SwapMint (gross, net, fee) and also emits Mint for LP issuance.
/// @param payer who transfers the input token /// @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 inputTokenIndex index of the input token
/// @param maxAmountIn maximum uint token input (inclusive of fee) /// @param maxAmountIn maximum uint token input (inclusive of fee)
/// @param deadline optional deadline /// @param deadline optional deadline
@@ -206,11 +212,11 @@ interface IPartyPool is IERC20Metadata {
uint256 deadline uint256 deadline
) external payable returns (uint256 lpMinted); ) external payable returns (uint256 lpMinted);
/// @notice Burn LP _tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. /// @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. /// @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 payer who burns LP tokens
/// @param receiver who receives the single asset /// @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 inputTokenIndex index of target asset to receive
/// @param deadline optional deadline /// @param deadline optional deadline
/// @return amountOutUint uint amount of asset inputTokenIndex sent to receiver /// @return amountOutUint uint amount of asset inputTokenIndex sent to receiver
@@ -225,9 +231,9 @@ interface IPartyPool is IERC20Metadata {
/** /**
* @dev Initiate a flash loan. * @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 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. * @param data Arbitrary data structure, intended to contain user-defined parameters.
*/ */
function flashLoan( function flashLoan(

62
src/OwnableExternal.sol Normal file
View File

@@ -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);
}
}

41
src/OwnableInternal.sol Normal file
View File

@@ -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);
}
}

View File

@@ -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 {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {IPartyPlanner} from "./IPartyPlanner.sol"; import {IPartyPlanner} from "./IPartyPlanner.sol";
import {IPartyPool} from "./IPartyPool.sol"; import {IPartyPool} from "./IPartyPool.sol";
import {NativeWrapper} from "./NativeWrapper.sol";
import {LMSRStabilized} from "./LMSRStabilized.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 {IPartyPoolDeployer} from "./PartyPoolDeployer.sol";
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol"; import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol"; import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
/// @title PartyPlanner /// @title PartyPlanner
/// @notice Factory contract for creating and tracking PartyPool instances /// @notice Factory contract for creating and tracking PartyPool instances
contract PartyPlanner is IPartyPlanner { contract PartyPlanner is OwnableExternal, IPartyPlanner {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
int128 private constant ONE = int128(1) << 64; int128 private constant ONE = int128(1) << 64;
@@ -46,32 +48,37 @@ contract PartyPlanner is IPartyPlanner {
mapping(IERC20 => bool) private _tokenSupported; mapping(IERC20 => bool) private _tokenSupported;
mapping(IERC20 => IPartyPool[]) private _poolsByToken; mapping(IERC20 => IPartyPool[]) private _poolsByToken;
/// @param _swapImpl address of the Swap implementation contract to be used by all pools /// @param owner_ Initial administrator who is allowed to create new pools and kill() old ones
/// @param _mintImpl address of the Mint implementation contract to be used by all pools /// @param wrapper_ The WETH9 implementation address used for this chain
/// @param _protocolFeePpm protocol fee share (ppm) to be used for pools created by this planner /// @param swapImpl_ address of the Swap implementation contract to be used by all pools
/// @param _protocolFeeAddress recipient address for protocol fees for pools created by this planner (may be address(0)) /// @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( constructor(
NativeWrapper _wrapper, address owner_,
PartyPoolSwapImpl _swapImpl, NativeWrapper wrapper_,
PartyPoolMintImpl _mintImpl, PartyPoolSwapImpl swapImpl_,
IPartyPoolDeployer _deployer, PartyPoolMintImpl mintImpl_,
IPartyPoolDeployer _balancedPairDeployer, IPartyPoolDeployer deployer_,
uint256 _protocolFeePpm, IPartyPoolDeployer balancedPairDeployer_,
address _protocolFeeAddress uint256 protocolFeePpm_,
) { address protocolFeeAddress_
WRAPPER = _wrapper; )
require(address(_swapImpl) != address(0), "Planner: swapImpl address cannot be zero"); OwnableExternal(owner_)
SWAP_IMPL = _swapImpl; {
require(address(_mintImpl) != address(0), "Planner: mintImpl address cannot be zero"); WRAPPER = wrapper_;
MINT_IMPL = _mintImpl; require(address(swapImpl_) != address(0), "Planner: swapImpl address cannot be zero");
require(address(_deployer) != address(0), "Planner: deployer address cannot be zero"); SWAP_IMPL = swapImpl_;
NORMAL_POOL_DEPLOYER = _deployer; require(address(mintImpl_) != address(0), "Planner: mintImpl address cannot be zero");
require(address(_balancedPairDeployer) != address(0), "Planner: balanced pair deployer address cannot be zero"); MINT_IMPL = mintImpl_;
BALANCED_PAIR_DEPLOYER = _balancedPairDeployer; 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"); require(protocolFeePpm_ < 1_000_000, "Planner: protocol fee >= ppm");
PROTOCOL_FEE_PPM = _protocolFeePpm; PROTOCOL_FEE_PPM = protocolFeePpm_;
PROTOCOL_FEE_ADDRESS = _protocolFeeAddress; PROTOCOL_FEE_ADDRESS = protocolFeeAddress_;
} }
/// Main newPool variant: accepts kappa directly (preferred). /// Main newPool variant: accepts kappa directly (preferred).
@@ -79,38 +86,39 @@ contract PartyPlanner is IPartyPlanner {
// Pool constructor args // Pool constructor args
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory _tokens, IERC20[] memory tokens_,
uint256[] memory _bases, uint256[] memory bases_,
int128 _kappa, int128 kappa_,
uint256 _swapFeePpm, uint256 swapFeePpm_,
uint256 _flashFeePpm, uint256 flashFeePpm_,
bool _stable, bool stable_,
// Initial deposit information // Initial deposit information
address payer, address payer,
address receiver, address receiver,
uint256[] memory initialDeposits, uint256[] memory initialDeposits,
uint256 initialLpAmount, uint256 initialLpAmount,
uint256 deadline uint256 deadline
) public returns (IPartyPool pool, uint256 lpAmount) { ) public onlyOwner returns (IPartyPool pool, uint256 lpAmount) {
// Validate inputs // Validate inputs
require(deadline == 0 || block.timestamp <= deadline, "Planner: deadline exceeded"); 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(payer != address(0), "Planner: payer cannot be zero address");
require(receiver != address(0), "Planner: receiver cannot be zero address"); require(receiver != address(0), "Planner: receiver cannot be zero address");
// Validate kappa > 0 (Q64.64) // 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) // 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( pool = deployer.deploy(
_owner, // Same owner as this PartyPlanner
name_, name_,
symbol_, symbol_,
_tokens, tokens_,
_bases, bases_,
_kappa, kappa_,
_swapFeePpm, swapFeePpm_,
_flashFeePpm, flashFeePpm_,
PROTOCOL_FEE_PPM, PROTOCOL_FEE_PPM,
PROTOCOL_FEE_ADDRESS, PROTOCOL_FEE_ADDRESS,
WRAPPER, WRAPPER,
@@ -122,8 +130,8 @@ contract PartyPlanner is IPartyPlanner {
_poolSupported[pool] = true; _poolSupported[pool] = true;
// Track _tokens and populate mappings // Track _tokens and populate mappings
for (uint256 i = 0; i < _tokens.length; i++) { for (uint256 i = 0; i < tokens_.length; i++) {
IERC20 token = _tokens[i]; IERC20 token = tokens_[i];
// Add token to _allTokens if not already present // Add token to _allTokens if not already present
if (!_tokenSupported[token]) { if (!_tokenSupported[token]) {
@@ -135,17 +143,17 @@ contract PartyPlanner is IPartyPlanner {
_poolsByToken[token].push(pool); _poolsByToken[token].push(pool);
} }
emit PartyStarted(pool, name_, symbol_, _tokens); emit PartyStarted(pool, name_, symbol_, tokens_);
// Transfer initial _tokens from payer to the pool // 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) { if (initialDeposits[i] > 0) {
IERC20(_tokens[i]).safeTransferFrom(payer, address(pool), initialDeposits[i]); IERC20(tokens_[i]).safeTransferFrom(payer, address(pool), initialDeposits[i]);
require(IERC20(_tokens[i]).balanceOf(address(pool)) == initialDeposits[i], 'fee-on-transfer tokens not supported'); 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); lpAmount = pool.initialMint(receiver, initialLpAmount);
} }
@@ -156,37 +164,37 @@ contract PartyPlanner is IPartyPlanner {
// Pool constructor args (old signature) // Pool constructor args (old signature)
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory _tokens, IERC20[] memory tokens_,
uint256[] memory _bases, uint256[] memory bases_,
int128 _tradeFrac, int128 tradeFrac_,
int128 _targetSlippage, int128 targetSlippage_,
uint256 _swapFeePpm, uint256 swapFeePpm_,
uint256 _flashFeePpm, uint256 flashFeePpm_,
bool _stable, bool stable_,
// Initial deposit information // Initial deposit information
address payer, address payer,
address receiver, address receiver,
uint256[] memory initialDeposits, uint256[] memory initialDeposits,
uint256 initialLpAmount, uint256 initialLpAmount,
uint256 deadline 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 // 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(tradeFrac_ < ONE, "Planner: tradeFrac must be < 1 (64.64)");
require(_targetSlippage < ONE, "Planner: targetSlippage 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) // 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 // Delegate to the kappa-based newPool variant
return newPool( return newPool(
name_, name_,
symbol_, symbol_,
_tokens, tokens_,
_bases, bases_,
computedKappa, computedKappa,
_swapFeePpm, swapFeePpm_,
_flashFeePpm, flashFeePpm_,
_stable, stable_,
payer, payer,
receiver, receiver,
initialDeposits, initialDeposits,

View File

@@ -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 {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC3156FlashLender} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol"; import {IERC3156FlashLender} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol";
import {NativeWrapper} from "./NativeWrapper.sol"; import {NativeWrapper} from "./NativeWrapper.sol";
import {OwnableExternal} from "./OwnableExternal.sol";
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token /// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model. /// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
/// The pool issues an ERC20 LP token representing proportional ownership. /// The pool issues an ERC20 LP token representing proportional ownership.
/// It supports: /// It supports:
/// - Proportional minting and burning of LP _tokens, /// - Proportional minting and burning of LP tokens,
/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap),
/// - Exact-input swaps and swaps-to-price-limits, /// - 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 /// @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. /// 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 /// 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). /// (i.e., floor outputs to users, ceil inputs/fees where appropriate). Mutating methods have re-entrancy locks.
contract PartyPool is PartyPoolBase, ERC20External, IPartyPool { /// 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 ABDKMath64x64 for int128;
using LMSRStabilized for LMSRStabilized.State; using LMSRStabilized for LMSRStabilized.State;
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
receive() external payable {} 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; } function wrapperToken() external view returns (NativeWrapper) { return WRAPPER_TOKEN; }
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q) /// @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; } 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 name_ LP token name
/// @param symbol_ LP token symbol /// @param symbol_ LP token symbol
/// @param tokens_ token addresses (n) /// @param tokens_ token addresses (n)
@@ -100,6 +107,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
/// @param swapImpl_ address of the SwapMint implementation contract /// @param swapImpl_ address of the SwapMint implementation contract
/// @param mintImpl_ address of the Mint implementation contract /// @param mintImpl_ address of the Mint implementation contract
constructor( constructor(
address owner_,
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory tokens_, IERC20[] memory tokens_,
@@ -114,8 +122,10 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
PartyPoolMintImpl mintImpl_ PartyPoolMintImpl mintImpl_
) )
PartyPoolBase(wrapperToken_) PartyPoolBase(wrapperToken_)
OwnableExternal(owner_)
ERC20External(name_, symbol_) ERC20External(name_, symbol_)
{ {
require(owner_ != address(0));
require(tokens_.length > 1, "Pool: need >1 asset"); require(tokens_.length > 1, "Pool: need >1 asset");
require(tokens_.length == bases_.length, "Pool: lengths mismatch"); require(tokens_.length == bases_.length, "Pool: lengths mismatch");
_tokens = tokens_; _tokens = tokens_;
@@ -149,6 +159,11 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
_protocolFeesOwed = new uint256[](n); _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) Initialization / Mint / Burn (LP token managed)
@@ -225,7 +240,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
int128 limitPrice, int128 limitPrice,
uint256 deadline, uint256 deadline,
bool unwrap 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"); require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
// Compute amounts using the same path as views // Compute amounts using the same path as views
@@ -425,7 +440,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
address tokenAddr, address tokenAddr,
uint256 amount, uint256 amount,
bytes calldata data bytes calldata data
) external nonReentrant returns (bool) ) external nonReentrant killable returns (bool)
{ {
IERC20 token = IERC20(tokenAddr); IERC20 token = IERC20(tokenAddr);
require(amount <= token.balanceOf(address(this))); require(amount <= token.balanceOf(address(this)));

View File

@@ -11,6 +11,7 @@ import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
contract PartyPoolBalancedPair is PartyPool { contract PartyPoolBalancedPair is PartyPool {
constructor( constructor(
address owner_,
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory tokens_, IERC20[] memory tokens_,
@@ -23,7 +24,8 @@ contract PartyPoolBalancedPair is PartyPool {
NativeWrapper wrapperToken_, NativeWrapper wrapperToken_,
PartyPoolSwapImpl swapMintImpl_, PartyPoolSwapImpl swapMintImpl_,
PartyPoolMintImpl mintImpl_ 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 function _swapAmountsForExactInput(uint256 i, uint256 j, int128 a, int128 limitPrice) internal virtual override view

View File

@@ -9,10 +9,11 @@ import {LMSRStabilized} from "./LMSRStabilized.sol";
import {PartyPoolHelpers} from "./PartyPoolHelpers.sol"; import {PartyPoolHelpers} from "./PartyPoolHelpers.sol";
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.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. /// @notice Abstract base contract that contains storage and internal helpers only.
/// No external/public functions or constructor here — derived implementations own immutables and constructors. /// No external/public functions here.
abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard, PartyPoolHelpers { abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGuard, PartyPoolHelpers {
using ABDKMath64x64 for int128; using ABDKMath64x64 for int128;
using LMSRStabilized for LMSRStabilized.State; using LMSRStabilized for LMSRStabilized.State;
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
@@ -23,18 +24,13 @@ abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard, PartyPoolHelp
WRAPPER_TOKEN = wrapper_; 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 // Internal state
modifier native() { //
_;
uint256 bal = address(this).balance;
if(bal > 0)
payable(msg.sender).transfer(bal);
}
// /// @notice If _killed is set, then all `killable` methods are permanently disabled, leaving only burns
// Internal state (no immutables here; immutables belong to derived contracts) /// (withdrawals) working
// bool internal _killed;
// LMSR internal state // LMSR internal state
LMSRStabilized.State internal _lmsr; LMSRStabilized.State internal _lmsr;
@@ -64,6 +60,20 @@ abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard, PartyPoolHelp
uint256[] internal _cachedUintBalances; 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) Conversion & fee helpers (internal)
---------------------- */ ---------------------- */

View File

@@ -11,6 +11,7 @@ import {PartyPoolBalancedPair} from "./PartyPoolBalancedPair.sol";
interface IPartyPoolDeployer { interface IPartyPoolDeployer {
function deploy( function deploy(
address owner_,
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory tokens_, IERC20[] memory tokens_,
@@ -28,6 +29,7 @@ interface IPartyPoolDeployer {
contract PartyPoolDeployer is IPartyPoolDeployer { contract PartyPoolDeployer is IPartyPoolDeployer {
function deploy( function deploy(
address owner_,
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory tokens_, IERC20[] memory tokens_,
@@ -42,6 +44,7 @@ contract PartyPoolDeployer is IPartyPoolDeployer {
PartyPoolMintImpl mintImpl_ PartyPoolMintImpl mintImpl_
) external returns (IPartyPool) { ) external returns (IPartyPool) {
return new PartyPool( return new PartyPool(
owner_,
name_, name_,
symbol_, symbol_,
tokens_, tokens_,
@@ -60,6 +63,7 @@ contract PartyPoolDeployer is IPartyPoolDeployer {
contract PartyPoolBalancedPairDeployer is IPartyPoolDeployer { contract PartyPoolBalancedPairDeployer is IPartyPoolDeployer {
function deploy( function deploy(
address owner_,
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory tokens_, IERC20[] memory tokens_,
@@ -74,6 +78,7 @@ contract PartyPoolBalancedPairDeployer is IPartyPoolDeployer {
PartyPoolMintImpl mintImpl_ PartyPoolMintImpl mintImpl_
) external returns (IPartyPool) { ) external returns (IPartyPool) {
return new PartyPoolBalancedPair( return new PartyPoolBalancedPair(
owner_,
name_, name_,
symbol_, symbol_,
tokens_, tokens_,

View File

@@ -25,7 +25,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Initialization Mint // 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) { returns (uint256 lpMinted) {
uint256 n = _tokens.length; uint256 n = _tokens.length;
@@ -65,7 +65,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Regular Mint and Burn // 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) { returns (uint256 lpMinted) {
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded"); require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
uint256 n = _tokens.length; uint256 n = _tokens.length;
@@ -131,7 +131,8 @@ contract PartyPoolMintImpl is PartyPoolBase {
return actualLpToMint; 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 /// @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. /// proportionally to reflect the reduced pool size after the withdrawal.
/// @param payer address that provides the LP _tokens to burn /// @param payer address that provides the LP _tokens to burn
@@ -345,7 +346,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
uint256 deadline, uint256 deadline,
uint256 swapFeePpm, uint256 swapFeePpm,
uint256 protocolFeePpm uint256 protocolFeePpm
) external payable native nonReentrant returns (uint256 lpMinted) { ) external payable native killable nonReentrant returns (uint256 lpMinted) {
uint256 n = _tokens.length; uint256 n = _tokens.length;
require(inputTokenIndex < n, "swapMint: idx"); require(inputTokenIndex < n, "swapMint: idx");
require(maxAmountIn > 0, "swapMint: input zero"); 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. /// @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. /// @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 payer who burns LP _tokens
/// @param receiver who receives the single asset /// @param receiver who receives the single asset
@@ -485,7 +488,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
bool unwrap, bool unwrap,
uint256 swapFeePpm, uint256 swapFeePpm,
uint256 protocolFeePpm uint256 protocolFeePpm
) external nonReentrant returns (uint256 amountOutUint) { ) external nonReentrant killable returns (uint256 amountOutUint) {
uint256 n = _tokens.length; uint256 n = _tokens.length;
require(inputTokenIndex < n, "burnSwap: idx"); require(inputTokenIndex < n, "burnSwap: idx");
require(lpAmount > 0, "burnSwap: zero lp"); require(lpAmount > 0, "burnSwap: zero lp");

View File

@@ -59,7 +59,7 @@ contract PartyPoolSwapImpl is PartyPoolBase {
bool unwrap, bool unwrap,
uint256 swapFeePpm, uint256 swapFeePpm,
uint256 protocolFeePpm 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; uint256 n = _tokens.length;
require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx"); require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
require(limitPrice > int128(0), "swapToLimit: limit <= 0"); require(limitPrice > int128(0), "swapToLimit: limit <= 0");

View File

@@ -18,11 +18,17 @@ library Deploy {
function newPartyPlanner() internal returns (PartyPlanner) { function newPartyPlanner() internal returns (PartyPlanner) {
NativeWrapper wrapper = new WETH9(); 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( return new PartyPlanner(
owner,
wrapper, wrapper,
new PartyPoolSwapImpl(wrapper), new PartyPoolSwapImpl(wrapper),
new PartyPoolMintImpl(wrapper), new PartyPoolMintImpl(wrapper),
@@ -34,6 +40,7 @@ library Deploy {
} }
function newPartyPool( function newPartyPool(
address owner_,
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory tokens_, IERC20[] memory tokens_,
@@ -44,10 +51,11 @@ library Deploy {
bool _stable bool _stable
) internal returns (PartyPool) { ) internal returns (PartyPool) {
NativeWrapper wrapper = new WETH9(); 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( function newPartyPool(
address owner_,
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
IERC20[] memory tokens_, IERC20[] memory tokens_,
@@ -60,6 +68,7 @@ library Deploy {
) internal returns (PartyPool) { ) internal returns (PartyPool) {
return _stable && tokens_.length == 2 ? return _stable && tokens_.length == 2 ?
new PartyPoolBalancedPair( new PartyPoolBalancedPair(
owner_,
name_, name_,
symbol_, symbol_,
tokens_, tokens_,
@@ -74,6 +83,7 @@ library Deploy {
new PartyPoolMintImpl(wrapper) new PartyPoolMintImpl(wrapper)
) : ) :
new PartyPool( new PartyPool(
owner_,
name_, name_,
symbol_, symbol_,
tokens_, tokens_,

View File

@@ -141,7 +141,7 @@ contract GasTest is Test {
} }
// Compute kappa from slippage params and number of _tokens, then construct pool with kappa // Compute kappa from slippage params and number of _tokens, then construct pool with kappa
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage); 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 // Transfer initial deposit amounts into pool before initial mint
for (uint256 i = 0; i < numTokens; i++) { for (uint256 i = 0; i < numTokens; i++) {
@@ -181,7 +181,7 @@ contract GasTest is Test {
ierc20Tokens[i] = IERC20(tokens[i]); ierc20Tokens[i] = IERC20(tokens[i]);
} }
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage); 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 // Transfer initial deposit amounts into pool before initial mint
for (uint256 i = 0; i < numTokens; i++) { for (uint256 i = 0; i < numTokens; i++) {

View File

@@ -94,7 +94,7 @@ contract NativeTest is Test {
uint256 feePpm = 1000; uint256 feePpm = 1000;
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); 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 // Transfer initial deposit amounts into pool
token0.transfer(address(pool), INIT_BAL); token0.transfer(address(pool), INIT_BAL);

View File

@@ -46,8 +46,8 @@ contract PartyPlannerTest is Test {
uint256 constant INITIAL_DEPOSIT_AMOUNT = 1000e18; uint256 constant INITIAL_DEPOSIT_AMOUNT = 1000e18;
function setUp() public { function setUp() public {
// Deploy PartyPlanner // Deploy PartyPlanner owned by this test contract
planner = Deploy.newPartyPlanner(); planner = Deploy.newPartyPlanner(address(this));
// Deploy mock _tokens // Deploy mock _tokens
tokenA = new MockERC20("Token A", "TKNA", 18); tokenA = new MockERC20("Token A", "TKNA", 18);

View File

@@ -173,7 +173,7 @@ contract PartyPoolTest is Test {
uint256 feePpm = 1000; uint256 feePpm = 1000;
int128 kappa3 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); 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) // Transfer initial deposit amounts into pool before initial mint (pool expects _tokens already in contract)
// We deposit equal amounts INIT_BAL for each token // 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); 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 // Mint additional _tokens for pool10 initial deposit
token0.mint(address(this), INIT_BAL); token0.mint(address(this), INIT_BAL);
@@ -992,11 +992,11 @@ contract PartyPoolTest is Test {
// Pool with default initialization (lpTokens = 0) // Pool with default initialization (lpTokens = 0)
int128 kappaDefault = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); 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) // Pool with custom initialization (lpTokens = custom amount)
int128 kappaCustom = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); 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 // Mint additional _tokens for both pools
token0.mint(address(this), INIT_BAL * 2); token0.mint(address(this), INIT_BAL * 2);
@@ -1068,9 +1068,9 @@ contract PartyPoolTest is Test {
uint256 feePpm = 1000; uint256 feePpm = 1000;
int128 kappaDefault2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage); 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); 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 // Mint additional _tokens
token0.mint(address(this), INIT_BAL * 4); token0.mint(address(this), INIT_BAL * 4);