rename to PartyInfo; README fees

This commit is contained in:
tim
2025-11-07 16:18:00 -04:00
parent ff9ed674f9
commit a08a928f7f
10 changed files with 84 additions and 73 deletions

View File

@@ -70,16 +70,27 @@ quotes on all pairs in the pool.
Deployment addresses for each chain may be found in `deployment/liqp-deployments.json`, and the `solc` output including ABI information is stored under `deployment/{chain_id}/v1/...` Deployment addresses for each chain may be found in `deployment/liqp-deployments.json`, and the `solc` output including ABI information is stored under `deployment/{chain_id}/v1/...`
The primary entrypoint for all Liquidity Party actions is the [PartyPlanner](src/IPartyPlanner.sol) contract, which is a singleton per chain. The `PartyPlanner` contract not only deploys new pools but also indexes the pools and their tokens for easy metadata discovery. After a pool is created or discovered using the `PartyPlanner`, it can be used to perform swaps, minting, and other actions according to the [IPartyPool](src/IPartyPlanner.sol) interface. Due to contract size limitations, most view methods for prices and swaps are available from a separate singleton contract, the [PartyPoolViewer](src/IPartyPoolViewer.sol). The primary entrypoint for all Liquidity Party actions is the [PartyPlanner](src/IPartyPlanner.sol) contract, which is a singleton per chain. The `PartyPlanner` contract not only deploys new pools but also indexes the pools and their tokens for easy metadata discovery. After a pool is created or discovered using the `PartyPlanner`, it can be used to perform swaps, minting, and other actions according to the [IPartyPool](src/IPartyPlanner.sol) interface. Due to contract size limitations, most view methods for prices and swaps are available from a separate singleton contract, [PartyInfo](src/IPartyInfo.sol).
# Implementation Notes # Implementation Notes
## Non-upgradable Proxy ## Non-upgradable Proxy
Due to contract size constraints, the `PartyPool` contract uses `DELEGATECALL` to invoke implementations on the singleton [PartyPoolSwapImpl](src/PartyPoolSwapImpl.sol) and [PartyPoolMintImpl](src/PartyPoolMintImpl.sol) contracts. This proxy pattern is NOT upgradable and the implementation contract addresses used by the pool are immutable. Views implemented in `PartyPoolViewer` have no delegation but simply accept the target pool as an argument, calculating prices etc. from public getters. Due to contract size constraints, the `PartyPool` contract uses `DELEGATECALL` to invoke implementations on the singleton [PartyPoolSwapImpl](src/PartyPoolSwapImpl.sol) and [PartyPoolMintImpl](src/PartyPoolMintImpl.sol) contracts. This proxy pattern is NOT upgradable and the implementation contract addresses used by the pool are immutable. Views implemented in `PartyInfo` have no delegation but simply accept the target pool as an argument, calculating prices etc. from public getters.
## Admin-Only Deployment ## Admin-Only Deployment
`PartyPlanner` allows only the admin to deploy new pools. This decision was made because Liquidity Party is a new protocol that does not support non-standard tokens such as fee-on-transfer tokens or rebasing tokens, and the selection of the `kappa` liquidity parameter is not straightforward for an average user. We hope to offer a version of Liquidity Party in the future that allows regular users to create their pools. `PartyPlanner` allows only the admin to deploy new pools. This decision was made because Liquidity Party is a new protocol that does not support non-standard tokens such as fee-on-transfer tokens or rebasing tokens, and the selection of the `kappa` liquidity parameter is not straightforward for an average user. We hope to offer a version of Liquidity Party in the future that allows regular users to create their pools.
## Killable Contracts ## Killable Contracts
PartyPools may be "killed" by their admin, in which case all swaps and mints are disabled, and the only modifying function allowed to be called is `burn()` to allow LP's to safely withdraw their funds. Killing is irreversible and intended to be used as a last-ditch safety measure in case a critical vulnerablility is discovered. PartyPools may be "killed" by their admin, in which case all swaps and mints are disabled, and the only modifying function allowed to be called is `burn()` to allow LP's to safely withdraw their funds. Killing is irreversible and intended to be used as a last-ditch safety measure in case a critical vulnerablility is discovered.
## Fee Mechanisms
Each asset in the pool has a fee associated with it, which is paid when that asset is involved in a swap.
The fee for a swap is the input asset fee plus the output asset fee, but this total fee percentage is taken only from the input amount, prior to the LMSR calculation. This results in more of the input asset being collected by the pool compared to the value of the output asset removed. In this way, the LP holders accumulate fee value implicitly in their prorata share of the pool's total assets.
For a swap-mint operation, only the input asset fee is charged on the input token. For a burn-swap, only the output asset fee is charged on the output token.
Flash loans are charged a single fee, the flash fee, no matter what asset is loaned.
Protocol fees are taken as a fraction of any LP fees earned, rounding down in favor of the LP stakers. Protocol fees accumulate in a separate account in the pool until the admin sends a collection transaction to sweep the fees. While protocol fees are waiting to be collected, those funds do not participate in liquidity operations or earn fees.

View File

@@ -65,7 +65,7 @@ token() {
} }
PARTY_PLANNER=$(contract PartyPlanner) PARTY_PLANNER=$(contract PartyPlanner)
PARTY_POOL_VIEWER=$(contract PartyPoolViewer) PARTY_INFO=$(contract PartyInfo)
PARTY_POOL_MINT_IMPL=$(contract PartyPoolMintImpl) PARTY_POOL_MINT_IMPL=$(contract PartyPoolMintImpl)
PARTY_POOL_SWAP_IMPL=$(contract PartyPoolSwapImpl) PARTY_POOL_SWAP_IMPL=$(contract PartyPoolSwapImpl)
PARTY_POOL_DEPLOYER=$(contract PartyPoolDeployer) PARTY_POOL_DEPLOYER=$(contract PartyPoolDeployer)
@@ -85,7 +85,7 @@ cp -r out $OUT/
jq -n \ jq -n \
--arg chainId "$CHAINID" \ --arg chainId "$CHAINID" \
--arg partyPlanner "$PARTY_PLANNER" \ --arg partyPlanner "$PARTY_PLANNER" \
--arg partyPoolViewer "$PARTY_POOL_VIEWER" \ --arg partyInfo "$PARTY_INFO" \
--arg partyPoolMintImpl "$PARTY_POOL_MINT_IMPL" \ --arg partyPoolMintImpl "$PARTY_POOL_MINT_IMPL" \
--arg partyPoolSwapImpl "$PARTY_POOL_SWAP_IMPL" \ --arg partyPoolSwapImpl "$PARTY_POOL_SWAP_IMPL" \
--arg partyPoolDeployer "$PARTY_POOL_DEPLOYER" \ --arg partyPoolDeployer "$PARTY_POOL_DEPLOYER" \
@@ -99,7 +99,7 @@ jq -n \
{ {
v1: { v1: {
PartyPlanner: $partyPlanner, PartyPlanner: $partyPlanner,
PartyPoolViewer: $partyPoolViewer, PartyInfo: $partyInfo,
PartyPoolMintImpl: $partyPoolMintImpl, PartyPoolMintImpl: $partyPoolMintImpl,
PartyPoolSwapImpl: $partyPoolSwapImpl, PartyPoolSwapImpl: $partyPoolSwapImpl,
PartyPoolDeployer: $partyPoolDeployer, PartyPoolDeployer: $partyPoolDeployer,
@@ -116,7 +116,7 @@ jq -n \
jq -n \ jq -n \
--arg chainId "$CHAINID" \ --arg chainId "$CHAINID" \
--arg partyPlanner "$PARTY_PLANNER" \ --arg partyPlanner "$PARTY_PLANNER" \
--arg partyPoolViewer "$PARTY_POOL_VIEWER" \ --arg partyInfo "$PARTY_INFO" \
--arg partyPoolMintImpl "$PARTY_POOL_MINT_IMPL" \ --arg partyPoolMintImpl "$PARTY_POOL_MINT_IMPL" \
--arg partyPoolSwapImpl "$PARTY_POOL_SWAP_IMPL" \ --arg partyPoolSwapImpl "$PARTY_POOL_SWAP_IMPL" \
--arg partyPoolDeployer "$PARTY_POOL_DEPLOYER" \ --arg partyPoolDeployer "$PARTY_POOL_DEPLOYER" \
@@ -129,7 +129,7 @@ jq -n \
'{($chainId): { '{($chainId): {
v1: { v1: {
PartyPlanner: $partyPlanner, PartyPlanner: $partyPlanner,
PartyPoolViewer: $partyPoolViewer, PartyInfo.sol: $partyInfo,
PartyPoolMintImpl: $partyPoolMintImpl, PartyPoolMintImpl: $partyPoolMintImpl,
PartyPoolSwapImpl: $partyPoolSwapImpl, PartyPoolSwapImpl: $partyPoolSwapImpl,
PartyPoolDeployer: $partyPoolDeployer, PartyPoolDeployer: $partyPoolDeployer,

View File

@@ -11,7 +11,7 @@ import {StdUtils} from "../lib/forge-std/src/StdUtils.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {PartyPlanner} from "../src/PartyPlanner.sol"; import {PartyPlanner} from "../src/PartyPlanner.sol";
import {PartyPoolViewer} from "../src/PartyPoolViewer.sol"; import {PartyInfo} from "../src/PartyInfo.sol";
import {Deploy} from "../test/Deploy.sol"; import {Deploy} from "../test/Deploy.sol";
import {MockERC20} from "../test/MockERC20.sol"; import {MockERC20} from "../test/MockERC20.sol";
@@ -163,7 +163,7 @@ contract DeployMock is Script {
0 0
); );
PartyPoolViewer viewer = Deploy.newViewer(); PartyInfo info = Deploy.newInfo();
// give _tokens to dev7 for later use // give _tokens to dev7 for later use
mintAll(DEV_ACCOUNT_7, 1_000_000); mintAll(DEV_ACCOUNT_7, 1_000_000);
@@ -172,9 +172,9 @@ contract DeployMock is Script {
// Set ENV vars // Set ENV vars
string memory plannerStr = vm.toString(address(planner)); string memory plannerStr = vm.toString(address(planner));
string memory viewerStr = vm.toString(address(viewer)); string memory infoStr = vm.toString(address(info));
vm.setEnv('PLANNER', plannerStr); vm.setEnv('PLANNER', plannerStr);
vm.setEnv('VIEWER', viewerStr); vm.setEnv('INFO', infoStr);
vm.setEnv('USXD', vm.toString(address(usxd))); vm.setEnv('USXD', vm.toString(address(usxd)));
vm.setEnv('FUSD', vm.toString(address(fusd))); vm.setEnv('FUSD', vm.toString(address(fusd)));
vm.setEnv('DIVE', vm.toString(address(dive))); vm.setEnv('DIVE', vm.toString(address(dive)));
@@ -183,19 +183,19 @@ contract DeployMock is Script {
// Write JSON config file // Write JSON config file
string memory chainConfigStr = vm.serializeString('config', 'PartyPlanner', plannerStr); string memory chainConfigStr = vm.serializeString('config', 'PartyPlanner', plannerStr);
chainConfigStr = vm.serializeString('config', 'PartyPoolViewer', viewerStr); chainConfigStr = vm.serializeString('config', 'PartyInfo', infoStr);
string memory v1ConfigStr = vm.serializeString('v1', 'v1', chainConfigStr); string memory v1ConfigStr = vm.serializeString('v1', 'v1', chainConfigStr);
string memory configStr = vm.serializeString('chain config', vm.toString(block.chainid), v1ConfigStr); string memory configStr = vm.serializeString('chain config', vm.toString(block.chainid), v1ConfigStr);
vm.writeJson(configStr, 'liqp-deployments.json'); vm.writeJson(configStr, 'liqp-deployments.json');
console2.log(); console2.log();
console2.log(' PartyPlanner', address(planner)); console2.log('PartyPlanner', address(planner));
console2.log('PartyPoolViewer', address(viewer)); console2.log(' PartyInfo', address(info));
console2.log(' USXD', address(usxd)); console2.log(' USXD', address(usxd));
console2.log(' FUSD', address(fusd)); console2.log(' FUSD', address(fusd));
console2.log(' DIVE', address(dive)); console2.log(' DIVE', address(dive));
console2.log(' BUTC', address(butc)); console2.log(' BUTC', address(butc));
console2.log(' WTETH', address(wteth)); console2.log(' WTETH', address(wteth));
} }
MockERC20 private usxd; MockERC20 private usxd;

View File

@@ -11,14 +11,14 @@ import {StdUtils} from "../lib/forge-std/src/StdUtils.sol";
import {IERC3156FlashBorrower} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol"; import {IERC3156FlashBorrower} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IPartyPool} from "../src/IPartyPool.sol"; import {IPartyPool} from "../src/IPartyPool.sol";
import {IPartyPoolViewer} from "../src/IPartyPoolViewer.sol"; import {IPartyInfo} from "../src/IPartyInfo.sol";
import {LMSRStabilized} from "../src/LMSRStabilized.sol"; import {LMSRStabilized} from "../src/LMSRStabilized.sol";
import {NativeWrapper} from "../src/NativeWrapper.sol"; import {NativeWrapper} from "../src/NativeWrapper.sol";
import {PartyPlanner} from "../src/PartyPlanner.sol"; import {PartyPlanner} from "../src/PartyPlanner.sol";
import {PartyPoolDeployer, PartyPoolBalancedPairDeployer} from "../src/PartyPoolDeployer.sol"; import {PartyPoolDeployer, PartyPoolBalancedPairDeployer} from "../src/PartyPoolDeployer.sol";
import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol"; import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol";
import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol"; import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol";
import {PartyPoolViewer} from "../src/PartyPoolViewer.sol"; import {PartyInfo} from "../src/PartyInfo.sol";
import {MockERC20} from "../test/MockERC20.sol"; import {MockERC20} from "../test/MockERC20.sol";
import {MockFlashBorrower} from "../test/MockFlashBorrower.sol"; import {MockFlashBorrower} from "../test/MockFlashBorrower.sol";
@@ -189,17 +189,17 @@ contract DeploySepolia is Script {
0 0
); );
PartyPoolViewer viewer = new PartyPoolViewer(swapImpl, mintImpl); PartyInfo info = new PartyInfo(swapImpl, mintImpl);
exercise(exercisePool, viewer); exercise(exercisePool, info);
vm.stopBroadcast(); vm.stopBroadcast();
// Set ENV vars // Set ENV vars
string memory plannerStr = vm.toString(address(planner)); string memory plannerStr = vm.toString(address(planner));
string memory viewerStr = vm.toString(address(viewer)); string memory infoStr = vm.toString(address(info));
vm.setEnv('PLANNER', plannerStr); vm.setEnv('PLANNER', plannerStr);
vm.setEnv('VIEWER', viewerStr); vm.setEnv('INFO', infoStr);
vm.setEnv('USXD', vm.toString(address(usxd))); vm.setEnv('USXD', vm.toString(address(usxd)));
vm.setEnv('FUSD', vm.toString(address(fusd))); vm.setEnv('FUSD', vm.toString(address(fusd)));
vm.setEnv('DIVE', vm.toString(address(dive))); vm.setEnv('DIVE', vm.toString(address(dive)));
@@ -207,18 +207,18 @@ contract DeploySepolia is Script {
vm.setEnv('WTETH', vm.toString(address(wteth))); vm.setEnv('WTETH', vm.toString(address(wteth)));
console2.log(); console2.log();
console2.log(' PartyPlanner', address(planner)); console2.log(' PartyPlanner', address(planner));
console2.log('PartyPoolViewer', address(viewer)); console2.log(' PartyInfo', address(info));
console2.log(' SwapImpl', address(swapImpl)); console2.log(' SwapImpl', address(swapImpl));
console2.log(' MintImpl', address(mintImpl)); console2.log(' MintImpl', address(mintImpl));
console2.log(' Deployer', address(deployer)); console2.log(' Deployer', address(deployer));
console2.log(' BPair Deployer', address(balancedPairDeployer)); console2.log('BPair Deployer', address(balancedPairDeployer));
console2.log(); console2.log();
console2.log(' USXD', address(usxd)); console2.log(' USXD', address(usxd));
console2.log(' FUSD', address(fusd)); console2.log(' FUSD', address(fusd));
console2.log(' DIVE', address(dive)); console2.log(' DIVE', address(dive));
console2.log(' BUTC', address(butc)); console2.log(' BUTC', address(butc));
console2.log(' WTETH', address(wteth)); console2.log(' WTETH', address(wteth));
} }
MockERC20 private usxd; MockERC20 private usxd;
@@ -243,7 +243,7 @@ contract DeploySepolia is Script {
wteth.approve(spender, type(uint256).max); wteth.approve(spender, type(uint256).max);
} }
function exercise( IPartyPool pool, IPartyPoolViewer viewer ) internal { function exercise( IPartyPool pool, IPartyInfo info) internal {
// gather tokens and denominators // gather tokens and denominators
IERC20[] memory tokens = pool.allTokens(); IERC20[] memory tokens = pool.allTokens();
uint256 n = tokens.length; uint256 n = tokens.length;
@@ -263,7 +263,7 @@ contract DeploySepolia is Script {
// deploy a temporary borrower that repays amount + fee back to the pool // deploy a temporary borrower that repays amount + fee back to the pool
MockFlashBorrower borrower = new MockFlashBorrower(); MockFlashBorrower borrower = new MockFlashBorrower();
uint256 flashAmt = 53 * 10**6; // arbitrary non-even amount uint256 flashAmt = 53 * 10**6; // arbitrary non-even amount
uint256 flashFee = viewer.flashFee(pool, address(tokens[0]), flashAmt); uint256 flashFee = info.flashFee(pool, address(tokens[0]), flashAmt);
// Mint enough to cover the flash fee // Mint enough to cover the flash fee
MockERC20(address(tokens[0])).mint(address(borrower), flashFee); MockERC20(address(tokens[0])).mint(address(borrower), flashFee);
// pass the pool address in data so borrower can repay back to this pool // pass the pool address in data so borrower can repay back to this pool

View File

@@ -3,7 +3,7 @@ pragma solidity ^0.8.30;
import {IPartyPool} from "./IPartyPool.sol"; import {IPartyPool} from "./IPartyPool.sol";
interface IPartyPoolViewer { interface IPartyInfo {
/// @notice Marginal price of `base` denominated in `quote` as Q64.64. /// @notice Marginal price of `base` denominated in `quote` as Q64.64.
/// @dev Returns the LMSR marginal price p_quote / p_base in ABDK 64.64 fixed-point format. /// @dev Returns the LMSR marginal price p_quote / p_base in ABDK 64.64 fixed-point format.
/// Useful for off-chain quoting; raw 64.64 value is returned (no scaling to token units). /// Useful for off-chain quoting; raw 64.64 value is returned (no scaling to token units).

View File

@@ -5,13 +5,13 @@ import "forge-std/console2.sol";
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol"; import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IPartyPool} from "./IPartyPool.sol"; import {IPartyPool} from "./IPartyPool.sol";
import {IPartyPoolViewer} from "./IPartyPoolViewer.sol"; import {IPartyInfo} from "./IPartyInfo.sol";
import {LMSRStabilized} from "./LMSRStabilized.sol"; import {LMSRStabilized} from "./LMSRStabilized.sol";
import {PartyPoolHelpers} from "./PartyPoolHelpers.sol"; import {PartyPoolHelpers} from "./PartyPoolHelpers.sol";
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol"; import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol"; import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer { contract PartyInfo is PartyPoolHelpers, IPartyInfo {
using ABDKMath64x64 for int128; using ABDKMath64x64 for int128;
PartyPoolSwapImpl immutable internal SWAP_IMPL; PartyPoolSwapImpl immutable internal SWAP_IMPL;

View File

@@ -19,7 +19,7 @@ import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/uti
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"; import {OwnableExternal} from "./OwnableExternal.sol";
import {IPartyPoolViewer} from "./IPartyPoolViewer.sol"; import {IPartyInfo} from "./IPartyInfo.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.

View File

@@ -9,7 +9,7 @@ import {PartyPoolBalancedPair} from "../src/PartyPoolBalancedPair.sol";
import {PartyPoolDeployer, PartyPoolBalancedPairDeployer} from "../src/PartyPoolDeployer.sol"; import {PartyPoolDeployer, PartyPoolBalancedPairDeployer} from "../src/PartyPoolDeployer.sol";
import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol"; import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol";
import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol"; import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol";
import {PartyPoolViewer} from "../src/PartyPoolViewer.sol"; import {PartyInfo} from "../src/PartyInfo.sol";
import {WETH9} from "./WETH9.sol"; import {WETH9} from "./WETH9.sol";
library Deploy { library Deploy {
@@ -99,8 +99,8 @@ library Deploy {
} }
function newViewer() internal returns (PartyPoolViewer) { function newInfo() internal returns (PartyInfo) {
NativeWrapper wrapper = new WETH9(); NativeWrapper wrapper = new WETH9();
return new PartyPoolViewer(new PartyPoolSwapImpl(wrapper), new PartyPoolMintImpl(wrapper)); return new PartyInfo(new PartyPoolSwapImpl(wrapper), new PartyPoolMintImpl(wrapper));
} }
} }

View File

@@ -10,7 +10,7 @@ import "../src/PartyPool.sol";
import {NativeWrapper} from "../src/NativeWrapper.sol"; import {NativeWrapper} from "../src/NativeWrapper.sol";
import {PartyPlanner} from "../src/PartyPlanner.sol"; import {PartyPlanner} from "../src/PartyPlanner.sol";
import {Deploy} from "./Deploy.sol"; import {Deploy} from "./Deploy.sol";
import {PartyPoolViewer} from "../src/PartyPoolViewer.sol"; import {PartyInfo} from "../src/PartyInfo.sol";
import {WETH9} from "./WETH9.sol"; import {WETH9} from "./WETH9.sol";
/// @notice Minimal ERC20 token for tests with an external mint function. /// @notice Minimal ERC20 token for tests with an external mint function.
@@ -41,7 +41,7 @@ contract NativeTest is Test {
TestERC20Native token1; TestERC20Native token1;
WETH9 weth; // WETH is our third token WETH9 weth; // WETH is our third token
PartyPool pool; PartyPool pool;
PartyPoolViewer viewer; PartyInfo info;
address alice; address alice;
address bob; address bob;
@@ -111,7 +111,7 @@ contract NativeTest is Test {
token0.mint(bob, INIT_BAL); token0.mint(bob, INIT_BAL);
token1.mint(bob, INIT_BAL); token1.mint(bob, INIT_BAL);
viewer = Deploy.newViewer(); info = Deploy.newInfo();
} }
/// @notice Helper to verify refunds work correctly /// @notice Helper to verify refunds work correctly
@@ -300,7 +300,7 @@ contract NativeTest is Test {
uint256 lpRequest = pool.totalSupply() / 10; // Request 10% of pool uint256 lpRequest = pool.totalSupply() / 10; // Request 10% of pool
// Get required deposit amounts // Get required deposit amounts
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest); uint256[] memory deposits = info.mintAmounts(pool, lpRequest);
vm.startPrank(alice); vm.startPrank(alice);
token0.approve(address(pool), type(uint256).max); token0.approve(address(pool), type(uint256).max);
@@ -329,7 +329,7 @@ contract NativeTest is Test {
/// @notice Test mint with excess native currency - verify refund /// @notice Test mint with excess native currency - verify refund
function testMintWithExcessNativeRefunded() public { function testMintWithExcessNativeRefunded() public {
uint256 lpRequest = pool.totalSupply() / 10; uint256 lpRequest = pool.totalSupply() / 10;
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest); uint256[] memory deposits = info.mintAmounts(pool, lpRequest);
vm.startPrank(alice); vm.startPrank(alice);
token0.approve(address(pool), type(uint256).max); token0.approve(address(pool), type(uint256).max);
@@ -365,7 +365,7 @@ contract NativeTest is Test {
uint256 lpToBurn = pool.totalSupply() / 10; uint256 lpToBurn = pool.totalSupply() / 10;
// Get expected withdraw amounts // Get expected withdraw amounts
uint256[] memory withdraws = viewer.burnAmounts(pool, lpToBurn); uint256[] memory withdraws = info.burnAmounts(pool, lpToBurn);
uint256 thisEthBefore = address(this).balance; uint256 thisEthBefore = address(this).balance;
uint256 expectedWethWithdraw = withdraws[2]; // WETH is index 2 uint256 expectedWethWithdraw = withdraws[2]; // WETH is index 2
@@ -391,7 +391,7 @@ contract NativeTest is Test {
/// @notice Test burn to a different receiver with native output /// @notice Test burn to a different receiver with native output
function testBurnToReceiverWithNativeOutput() public { function testBurnToReceiverWithNativeOutput() public {
uint256 lpToBurn = pool.totalSupply() / 10; uint256 lpToBurn = pool.totalSupply() / 10;
uint256[] memory withdraws = viewer.burnAmounts(pool, lpToBurn); uint256[] memory withdraws = info.burnAmounts(pool, lpToBurn);
uint256 bobEthBefore = bob.balance; uint256 bobEthBefore = bob.balance;
uint256 bobToken0Before = token0.balanceOf(bob); uint256 bobToken0Before = token0.balanceOf(bob);
@@ -531,7 +531,7 @@ contract NativeTest is Test {
// 1. Mint with native currency // 1. Mint with native currency
uint256 lpRequest = pool.totalSupply() / 20; // 5% of pool uint256 lpRequest = pool.totalSupply() / 20; // 5% of pool
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest); uint256[] memory deposits = info.mintAmounts(pool, lpRequest);
token0.approve(address(pool), type(uint256).max); token0.approve(address(pool), type(uint256).max);
token1.approve(address(pool), type(uint256).max); token1.approve(address(pool), type(uint256).max);

View File

@@ -12,7 +12,7 @@ import "../src/PartyPool.sol";
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol"; import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
import {PartyPlanner} from "../src/PartyPlanner.sol"; import {PartyPlanner} from "../src/PartyPlanner.sol";
import {Deploy} from "./Deploy.sol"; import {Deploy} from "./Deploy.sol";
import {PartyPoolViewer} from "../src/PartyPoolViewer.sol"; import {PartyInfo} from "../src/PartyInfo.sol";
/// @notice Test contract that implements the flash callback for testing flash loans /// @notice Test contract that implements the flash callback for testing flash loans
contract FlashBorrower is IERC3156FlashBorrower { contract FlashBorrower is IERC3156FlashBorrower {
@@ -113,7 +113,7 @@ contract PartyPoolTest is Test {
PartyPlanner planner; PartyPlanner planner;
PartyPool pool; PartyPool pool;
PartyPool pool10; PartyPool pool10;
PartyPoolViewer viewer; PartyInfo info;
address alice; address alice;
address bob; address bob;
@@ -244,7 +244,7 @@ contract PartyPoolTest is Test {
token8.mint(bob, INIT_BAL); token8.mint(bob, INIT_BAL);
token9.mint(bob, INIT_BAL); token9.mint(bob, INIT_BAL);
viewer = Deploy.newViewer(); info = Deploy.newInfo();
} }
/// @notice Basic sanity: initial mint should have produced LP _tokens for this contract and the pool holds _tokens. /// @notice Basic sanity: initial mint should have produced LP _tokens for this contract and the pool holds _tokens.
@@ -286,7 +286,7 @@ contract PartyPoolTest is Test {
token2.approve(address(pool), type(uint256).max); token2.approve(address(pool), type(uint256).max);
// Inspect the deposit amounts that the pool will require (these are rounded up) // Inspect the deposit amounts that the pool will require (these are rounded up)
uint256[] memory deposits = viewer.mintAmounts(pool, 1); uint256[] memory deposits = info.mintAmounts(pool, 1);
// Basic sanity: deposits array length must match token count and not all zero necessarily // Basic sanity: deposits array length must match token count and not all zero necessarily
assertEq(deposits.length, 3); assertEq(deposits.length, 3);
@@ -328,7 +328,7 @@ contract PartyPoolTest is Test {
uint256 totalLpBefore = pool.totalSupply(); uint256 totalLpBefore = pool.totalSupply();
// Compute required deposits and perform mint for 1 wei // Compute required deposits and perform mint for 1 wei
uint256[] memory deposits = viewer.mintAmounts(pool, 1); uint256[] memory deposits = info.mintAmounts(pool, 1);
// Sum deposits as deposited_value // Sum deposits as deposited_value
uint256 depositedValue = 0; uint256 depositedValue = 0;
@@ -369,7 +369,7 @@ contract PartyPoolTest is Test {
// Request half of LP supply // Request half of LP supply
uint256 want = totalLp / 2; uint256 want = totalLp / 2;
uint256[] memory deposits = viewer.mintAmounts(pool, want); uint256[] memory deposits = info.mintAmounts(pool, want);
// We expect each deposit to be roughly half the pool balance, but due to rounding up it should satisfy: // We expect each deposit to be roughly half the pool balance, but due to rounding up it should satisfy:
// deposits[i] * 2 >= cached balance (i.e., rounding up) // deposits[i] * 2 >= cached balance (i.e., rounding up)
@@ -386,7 +386,7 @@ contract PartyPoolTest is Test {
assertTrue(totalLp > 0, "precondition: LP > 0"); assertTrue(totalLp > 0, "precondition: LP > 0");
// Compute amounts required to redeem entire supply (should be current balances) // Compute amounts required to redeem entire supply (should be current balances)
uint256[] memory withdrawAmounts = viewer.burnAmounts(pool, totalLp); uint256[] memory withdrawAmounts = info.burnAmounts(pool, totalLp);
// Sanity: withdrawAmounts should equal pool balances (or very close due to rounding) // Sanity: withdrawAmounts should equal pool balances (or very close due to rounding)
for (uint i = 0; i < withdrawAmounts.length; i++) { for (uint i = 0; i < withdrawAmounts.length; i++) {
@@ -490,7 +490,7 @@ contract PartyPoolTest is Test {
if (req == 0) req = 1; if (req == 0) req = 1;
// Compute expected deposit amounts via view // Compute expected deposit amounts via view
uint256[] memory expected = viewer.mintAmounts(pool, req); uint256[] memory expected = info.mintAmounts(pool, req);
// Ensure alice has _tokens and approve pool // Ensure alice has _tokens and approve pool
vm.startPrank(alice); vm.startPrank(alice);
@@ -535,7 +535,7 @@ contract PartyPoolTest is Test {
uint256 req = requests[k]; uint256 req = requests[k];
if (req == 0) req = 1; if (req == 0) req = 1;
uint256[] memory expected = viewer.mintAmounts(pool10, req); uint256[] memory expected = info.mintAmounts(pool10, req);
// Approve all _tokens from alice // Approve all _tokens from alice
vm.startPrank(alice); vm.startPrank(alice);
@@ -613,7 +613,7 @@ contract PartyPoolTest is Test {
} }
// Recompute withdraw amounts via view after any top-up // Recompute withdraw amounts via view after any top-up
uint256[] memory expected = viewer.burnAmounts(pool, req); uint256[] memory expected = info.burnAmounts(pool, req);
// If expected withdraws are all zero (rounding edge), skip this iteration // If expected withdraws are all zero (rounding edge), skip this iteration
if (expected[0] == 0 && expected[1] == 0 && expected[2] == 0) { if (expected[0] == 0 && expected[1] == 0 && expected[2] == 0) {
@@ -670,7 +670,7 @@ contract PartyPoolTest is Test {
vm.stopPrank(); vm.stopPrank();
} }
uint256[] memory expected = viewer.burnAmounts(pool10, req); uint256[] memory expected = info.burnAmounts(pool10, req);
// If expected withdraws are all zero (rounding edge), skip this iteration // If expected withdraws are all zero (rounding edge), skip this iteration
bool allZero = true; bool allZero = true;
@@ -949,7 +949,7 @@ contract PartyPoolTest is Test {
for (uint256 i = 0; i < testAmounts.length; i++) { for (uint256 i = 0; i < testAmounts.length; i++) {
uint256 amount = testAmounts[i]; uint256 amount = testAmounts[i];
uint256 fee = viewer.flashFee(pool, address(token0), amount); uint256 fee = info.flashFee(pool, address(token0), amount);
// Calculate expected fee // Calculate expected fee
uint256 expectedFee = (amount * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceiling uint256 expectedFee = (amount * pool.flashFeePpm() + 1_000_000 - 1) / 1_000_000; // ceiling
@@ -1096,8 +1096,8 @@ contract PartyPoolTest is Test {
token2.approve(address(poolCustom), type(uint256).max); token2.approve(address(poolCustom), type(uint256).max);
// Get required deposit amounts for both pools // Get required deposit amounts for both pools
uint256[] memory depositsDefault = viewer.mintAmounts(poolDefault, lpRequestDefault); uint256[] memory depositsDefault = info.mintAmounts(poolDefault, lpRequestDefault);
uint256[] memory depositsCustom = viewer.mintAmounts(poolCustom, lpRequestCustom); uint256[] memory depositsCustom = info.mintAmounts(poolCustom, lpRequestCustom);
// Deposits should be identical (same proportion of identical balances) // Deposits should be identical (same proportion of identical balances)
assertEq(depositsDefault[0], depositsCustom[0], "Token0 deposits should be identical"); assertEq(depositsDefault[0], depositsCustom[0], "Token0 deposits should be identical");
@@ -1126,8 +1126,8 @@ contract PartyPoolTest is Test {
/// @notice Verify that the initial relative price between token0 and token1 is 1.0000000 /// @notice Verify that the initial relative price between token0 and token1 is 1.0000000
function testInitialPriceIsOne() public view { function testInitialPriceIsOne() public view {
// Query the viewer for the relative price between token index 0 and 1 // Query the info viewer for the relative price between token index 0 and 1
int128 price = viewer.price(pool, 0, 1); int128 price = info.price(pool, 0, 1);
// Expected price is 1.0 in ABDK 64.64 fixed point // Expected price is 1.0 in ABDK 64.64 fixed point
int128 expected = ABDKMath64x64.fromInt(1); int128 expected = ABDKMath64x64.fromInt(1);
@@ -1137,8 +1137,8 @@ contract PartyPoolTest is Test {
/// @notice Verify that the initial LP price in terms of token0 is 1.0000000 /// @notice Verify that the initial LP price in terms of token0 is 1.0000000
function testInitialPoolPriceIsOne() public { function testInitialPoolPriceIsOne() public {
// Query the viewer for the pool price for token0 // Query the info viewer for the pool price for token0
int128 price = viewer.poolPrice(pool, 0); int128 price = info.poolPrice(pool, 0);
// Expected price is 1.0 in ABDK 64.64 fixed point // Expected price is 1.0 in ABDK 64.64 fixed point
int128 expected = ABDKMath64x64.fromInt(1); int128 expected = ABDKMath64x64.fromInt(1);
@@ -1157,7 +1157,7 @@ contract PartyPoolTest is Test {
if (lpRequest == 0) lpRequest = 1; if (lpRequest == 0) lpRequest = 1;
// Compute required deposits and perform mint if not trivial // Compute required deposits and perform mint if not trivial
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest); uint256[] memory deposits = info.mintAmounts(pool, lpRequest);
bool allZero = true; bool allZero = true;
for (uint i = 0; i < deposits.length; i++) { if (deposits[i] != 0) { allZero = false; break; } } for (uint i = 0; i < deposits.length; i++) { if (deposits[i] != 0) { allZero = false; break; } }
@@ -1167,7 +1167,7 @@ contract PartyPoolTest is Test {
vm.stopPrank(); vm.stopPrank();
// Re-query the pool price and ensure it remains 1.0 (within exact fixed-point equality) // Re-query the pool price and ensure it remains 1.0 (within exact fixed-point equality)
int128 priceAfter = viewer.poolPrice(pool, 0); int128 priceAfter = info.poolPrice(pool, 0);
assertEq(uint256(uint128(priceAfter)), uint256(uint128(expected)), "Pool price should remain 1.0000000 after mint"); assertEq(uint256(uint128(priceAfter)), uint256(uint128(expected)), "Pool price should remain 1.0000000 after mint");
} }