Deploy; PartyPoolBalancedPair as separate contract

This commit is contained in:
tim
2025-09-29 18:05:04 -04:00
parent a43c893609
commit 43fb62c47c
9 changed files with 324 additions and 81 deletions

View File

@@ -9,7 +9,7 @@ remappings = [
optimizer=true optimizer=true
optimizer_runs=999999999 optimizer_runs=999999999
viaIR=true viaIR=true
gas_reports = ['PartyPool', 'PartyPlanner'] gas_reports = ['PartyPlanner', 'PartyPoolBalancedPair', 'PartyPool', ]
fs_permissions = [{ access = "write", path = "chain.json"}] fs_permissions = [{ access = "write", path = "chain.json"}]
[lint] [lint]

30
src/Deploy.sol Normal file
View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30;
import "./PartyPoolBalancedPair.sol";
import {PartyPlanner} from "./PartyPlanner.sol";
import {PartyPool} from "./PartyPool.sol";
library Deploy {
function newPartyPlanner() internal returns (PartyPlanner) {
return new PartyPlanner();
}
function newPartyPool(
string memory name_,
string memory symbol_,
IERC20[] memory tokens_,
uint256[] memory bases_,
int128 kappa_,
uint256 swapFeePpm_,
uint256 flashFeePpm_,
bool stablePair_
) internal returns (PartyPool) {
return stablePair_ ?
new PartyPoolBalancedPair(name_, symbol_, tokens_, bases_, kappa_, swapFeePpm_, flashFeePpm_) :
new PartyPool(name_, symbol_, tokens_, bases_, kappa_, swapFeePpm_, flashFeePpm_);
}
}

View File

@@ -2,8 +2,9 @@
pragma solidity ^0.8.30; pragma solidity ^0.8.30;
import "./IPartyPlanner.sol"; import "./IPartyPlanner.sol";
import "./PartyPool.sol";
import "./LMSRStabilized.sol"; import "./LMSRStabilized.sol";
import "./PartyPool.sol";
import "./PartyPoolBalancedPair.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -48,16 +49,9 @@ contract PartyPlanner is IPartyPlanner {
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)
pool = new PartyPool( pool = _stable && _tokens.length == 2 ?
name_, new PartyPoolBalancedPair(name_, symbol_, _tokens, _bases, _kappa, _swapFeePpm, _flashFeePpm) :
symbol_, new PartyPool(name_, symbol_, _tokens, _bases, _kappa, _swapFeePpm, _flashFeePpm);
_tokens,
_bases,
_kappa,
_swapFeePpm,
_flashFeePpm,
_stable
);
_allPools.push(pool); _allPools.push(pool);
_poolSupported[pool] = true; _poolSupported[pool] = true;

View File

@@ -32,9 +32,6 @@ contract PartyPool is PoolBase, IPartyPool {
/// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts. /// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
uint256 public immutable flashFeePpm; uint256 public immutable flashFeePpm;
/// @notice If true and there are exactly two assets, an optimized 2-asset stable-pair path is used for some computations.
bool private immutable _stablePair; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled
/// @inheritdoc IPartyPool /// @inheritdoc IPartyPool
function tokens(uint256 i) external view returns (IERC20) { return s.tokens[i]; } function tokens(uint256 i) external view returns (IERC20) { return s.tokens[i]; }
@@ -65,7 +62,6 @@ contract PartyPool is PoolBase, IPartyPool {
/// @param kappa_ liquidity parameter κ (Q64.64) used to derive b = κ * S(q) /// @param kappa_ liquidity parameter κ (Q64.64) 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.
constructor( constructor(
string memory name_, string memory name_,
string memory symbol_, string memory symbol_,
@@ -73,15 +69,13 @@ contract PartyPool is PoolBase, IPartyPool {
uint256[] memory bases_, uint256[] memory bases_,
int128 kappa_, int128 kappa_,
uint256 swapFeePpm_, uint256 swapFeePpm_,
uint256 flashFeePpm_, uint256 flashFeePpm_
bool stable_
) ERC20(name_, symbol_) { ) ERC20(name_, symbol_) {
kappa = kappa_; kappa = kappa_;
require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm"); require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm");
swapFeePpm = swapFeePpm_; swapFeePpm = swapFeePpm_;
require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm"); require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm");
flashFeePpm = flashFeePpm_; flashFeePpm = flashFeePpm_;
_stablePair = stable_ && tokens_.length == 2;
// Initialize state using library // Initialize state using library
s.initialize(tokens_, bases_); s.initialize(tokens_, bases_);
@@ -155,8 +149,8 @@ contract PartyPool is PoolBase, IPartyPool {
uint256 outputTokenIndex, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) { ) virtual external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
return s.swapAmounts(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm, _stablePair); return s.swapAmounts(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm);
} }
/// @inheritdoc IPartyPool /// @inheritdoc IPartyPool
@@ -176,17 +170,7 @@ contract PartyPool is PoolBase, IPartyPool {
return s.swapMintAmounts(inputTokenIndex, maxAmountIn, swapFeePpm, totalSupply()); return s.swapMintAmounts(inputTokenIndex, maxAmountIn, swapFeePpm, totalSupply());
} }
/// @notice Swap input token i -> token j. Payer must approve token i. /// @inheritdoc IPartyPool
/// @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.
/// @param payer address of the account that pays for the swap
/// @param receiver address that will receive the output tokens
/// @param inputTokenIndex index of input asset
/// @param outputTokenIndex index of output asset
/// @param maxAmountIn maximum amount of token i (uint256) to transfer in (inclusive of fees)
/// @param limitPrice maximum acceptable marginal price (64.64 fixed point). Pass 0 to ignore.
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
/// @return amountIn actual input used (uint256), amountOut actual output sent (uint256), fee fee taken from the input (uint256)
function swap( function swap(
address payer, address payer,
address receiver, address receiver,
@@ -195,8 +179,8 @@ contract PartyPool is PoolBase, IPartyPool {
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) { ) virtual external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
return s.swap(payer, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, swapFeePpm, _stablePair); return s.swap(payer, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, swapFeePpm);
} }
/// @notice Swap up to the price limit; computes max input to reach limit then performs swap. /// @notice Swap up to the price limit; computes max input to reach limit then performs swap.

View File

@@ -0,0 +1,42 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30;
import "./PartyPool.sol";
import {PoolLibBalancedPair} from "./PoolLibBalancedPair.sol";
contract PartyPoolBalancedPair is PartyPool {
constructor(
string memory name_,
string memory symbol_,
IERC20[] memory tokens_,
uint256[] memory bases_,
int128 kappa_,
uint256 swapFeePpm_,
uint256 flashFeePpm_
) PartyPool(name_, symbol_, tokens_, bases_, kappa_, swapFeePpm_, flashFeePpm_) {
}
function swapAmounts(
uint256 inputTokenIndex,
uint256 outputTokenIndex,
uint256 maxAmountIn,
int128 limitPrice
) virtual override external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
return PoolLibBalancedPair.swapAmounts(
s, inputTokenIndex, outputTokenIndex,
maxAmountIn, limitPrice, swapFeePpm);
}
function swap(
address payer,
address receiver,
uint256 inputTokenIndex,
uint256 outputTokenIndex,
uint256 maxAmountIn,
int128 limitPrice,
uint256 deadline
) virtual override external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
return PoolLibBalancedPair.swap(s, payer, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, swapFeePpm);
}
}

View File

@@ -51,7 +51,7 @@ library PoolLib {
/// @param tokens_ Array of token addresses /// @param tokens_ Array of token addresses
/// @param bases_ Array of base denominators for each token /// @param bases_ Array of base denominators for each token
function initialize( function initialize(
State storage state, PoolLib.State storage state,
IERC20[] memory tokens_, IERC20[] memory tokens_,
uint256[] memory bases_ uint256[] memory bases_
) internal { ) internal {
@@ -78,7 +78,7 @@ library PoolLib {
/// @notice Get deposit amounts needed for minting LP tokens /// @notice Get deposit amounts needed for minting LP tokens
function mintDepositAmounts( function mintDepositAmounts(
State storage state, PoolLib.State storage state,
uint256 lpTokenAmount, uint256 lpTokenAmount,
uint256 totalSupply uint256 totalSupply
) internal view returns (uint256[] memory depositAmounts) { ) internal view returns (uint256[] memory depositAmounts) {
@@ -102,7 +102,7 @@ library PoolLib {
/// @notice Initial mint to set up pool for the first time /// @notice Initial mint to set up pool for the first time
function initialMint( function initialMint(
State storage state, PoolLib.State storage state,
address receiver, address receiver,
uint256 lpTokens, uint256 lpTokens,
int128 kappa, int128 kappa,
@@ -142,7 +142,7 @@ library PoolLib {
/// @notice Proportional mint for existing pool /// @notice Proportional mint for existing pool
function mint( function mint(
State storage state, PoolLib.State storage state,
address payer, address payer,
address receiver, address receiver,
uint256 lpTokenAmount, uint256 lpTokenAmount,
@@ -209,7 +209,7 @@ library PoolLib {
/// @notice Get withdrawal amounts for burning LP tokens /// @notice Get withdrawal amounts for burning LP tokens
function burnReceiveAmounts( function burnReceiveAmounts(
State storage state, PoolLib.State storage state,
uint256 lpTokenAmount, uint256 lpTokenAmount,
uint256 totalSupply uint256 totalSupply
) internal view returns (uint256[] memory withdrawAmounts) { ) internal view returns (uint256[] memory withdrawAmounts) {
@@ -230,7 +230,7 @@ library PoolLib {
/// @notice Burn LP tokens and withdraw proportional basket /// @notice Burn LP tokens and withdraw proportional basket
function burn( function burn(
State storage state, PoolLib.State storage state,
address payer, address payer,
address receiver, address receiver,
uint256 lpAmount, uint256 lpAmount,
@@ -293,23 +293,22 @@ library PoolLib {
/// @notice Get swap amounts for exact input swap /// @notice Get swap amounts for exact input swap
function swapAmounts( function swapAmounts(
State storage state, PoolLib.State storage state,
uint256 inputTokenIndex, uint256 inputTokenIndex,
uint256 outputTokenIndex, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 swapFeePpm, uint256 swapFeePpm
bool stablePair
) internal view returns (uint256 amountIn, uint256 amountOut, uint256 fee) { ) internal view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn( (uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(
state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm, stablePair state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm
); );
return (grossIn, outUint, feeUint); return (grossIn, outUint, feeUint);
} }
/// @notice Get swap amounts for swap to price limit /// @notice Get swap amounts for swap to price limit
function swapToLimitAmounts( function swapToLimitAmounts(
State storage state, PoolLib.State storage state,
uint256 inputTokenIndex, uint256 inputTokenIndex,
uint256 outputTokenIndex, uint256 outputTokenIndex,
int128 limitPrice, int128 limitPrice,
@@ -323,7 +322,7 @@ library PoolLib {
/// @notice Get amounts for swapMint operation /// @notice Get amounts for swapMint operation
function swapMintAmounts( function swapMintAmounts(
State storage state, PoolLib.State storage state,
uint256 inputTokenIndex, uint256 inputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
uint256 swapFeePpm, uint256 swapFeePpm,
@@ -381,7 +380,7 @@ library PoolLib {
/// @notice Execute exact input swap /// @notice Execute exact input swap
function swap( function swap(
State storage state, PoolLib.State storage state,
address payer, address payer,
address receiver, address receiver,
uint256 inputTokenIndex, uint256 inputTokenIndex,
@@ -389,8 +388,7 @@ library PoolLib {
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 deadline, uint256 deadline,
uint256 swapFeePpm, uint256 swapFeePpm
bool stablePair
) internal returns (uint256 amountIn, uint256 amountOut, uint256 fee) { ) internal returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
uint256 n = state.tokens.length; uint256 n = state.tokens.length;
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx"); require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
@@ -403,7 +401,7 @@ library PoolLib {
// Compute amounts // Compute amounts
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) = (uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) =
_quoteSwapExactIn(state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm, stablePair); _quoteSwapExactIn(state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm);
// Transfer exact amount from payer // Transfer exact amount from payer
state.tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransferAmount); state.tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransferAmount);
@@ -429,7 +427,7 @@ library PoolLib {
/// @notice Execute swap to price limit /// @notice Execute swap to price limit
function swapToLimit( function swapToLimit(
State storage state, PoolLib.State storage state,
address payer, address payer,
address receiver, address receiver,
uint256 inputTokenIndex, uint256 inputTokenIndex,
@@ -475,7 +473,7 @@ library PoolLib {
/// @notice Get amounts for burnSwap operation /// @notice Get amounts for burnSwap operation
function burnSwapAmounts( function burnSwapAmounts(
State storage state, PoolLib.State storage state,
uint256 lpAmount, uint256 lpAmount,
uint256 inputTokenIndex, uint256 inputTokenIndex,
uint256 swapFeePpm, uint256 swapFeePpm,
@@ -508,7 +506,7 @@ library PoolLib {
/// @notice Single-token mint (swapMint) /// @notice Single-token mint (swapMint)
function swapMint( function swapMint(
State storage state, PoolLib.State storage state,
address payer, address payer,
address receiver, address receiver,
uint256 inputTokenIndex, uint256 inputTokenIndex,
@@ -557,7 +555,7 @@ library PoolLib {
/// @notice Burn LP tokens and swap to single asset (burnSwap) /// @notice Burn LP tokens and swap to single asset (burnSwap)
function burnSwap( function burnSwap(
State storage state, PoolLib.State storage state,
address payer, address payer,
address receiver, address receiver,
uint256 lpAmount, uint256 lpAmount,
@@ -607,7 +605,7 @@ library PoolLib {
/// @notice Calculate flash loan repayment amounts /// @notice Calculate flash loan repayment amounts
function flashRepaymentAmounts( function flashRepaymentAmounts(
State storage state, PoolLib.State storage state,
uint256[] memory loanAmounts, uint256[] memory loanAmounts,
uint256 flashFeePpm uint256 flashFeePpm
) internal view returns (uint256[] memory repaymentAmounts) { ) internal view returns (uint256[] memory repaymentAmounts) {
@@ -622,7 +620,7 @@ library PoolLib {
/// @notice Execute flash loan /// @notice Execute flash loan
function flash( function flash(
State storage state, PoolLib.State storage state,
address recipient, address recipient,
uint256[] memory amounts, uint256[] memory amounts,
bytes calldata data, bytes calldata data,
@@ -683,7 +681,7 @@ library PoolLib {
/// @notice Get marginal price between two tokens /// @notice Get marginal price between two tokens
function price( function price(
State storage state, PoolLib.State storage state,
uint256 baseTokenIndex, uint256 baseTokenIndex,
uint256 quoteTokenIndex uint256 quoteTokenIndex
) internal view returns (int128) { ) internal view returns (int128) {
@@ -695,7 +693,7 @@ library PoolLib {
/// @notice Get price of one LP token in quote asset /// @notice Get price of one LP token in quote asset
function poolPrice( function poolPrice(
State storage state, PoolLib.State storage state,
uint256 quoteTokenIndex, uint256 quoteTokenIndex,
uint256 totalSupply uint256 totalSupply
) internal view returns (int128) { ) internal view returns (int128) {
@@ -747,13 +745,12 @@ library PoolLib {
/// @notice Internal quote for exact-input swap /// @notice Internal quote for exact-input swap
function _quoteSwapExactIn( function _quoteSwapExactIn(
State storage state, PoolLib.State storage state,
uint256 inputTokenIndex, uint256 inputTokenIndex,
uint256 outputTokenIndex, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 swapFeePpm, uint256 swapFeePpm
bool stablePair
) )
internal internal
view view
@@ -779,9 +776,64 @@ library PoolLib {
require(deltaInternalI > int128(0), "swap: input too small after fee"); require(deltaInternalI > int128(0), "swap: input too small after fee");
// Compute internal amounts using LMSR // Compute internal amounts using LMSR
(amountInInternalUsed, amountOutInternal) = (amountInInternalUsed, amountOutInternal) = state.lmsr.swapAmountsForExactInput(
stablePair ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(state.lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice) inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
: state.lmsr.swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
// Convert actual used input internal -> uint (ceil)
amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, state.bases[inputTokenIndex]);
require(amountInUintNoFee > 0, "swap: input zero");
// Compute gross transfer including fee
feeUint = 0;
grossIn = amountInUintNoFee;
if (swapFeePpm > 0) {
feeUint = _ceilFee(amountInUintNoFee, swapFeePpm);
grossIn += feeUint;
}
// Ensure within user max
require(grossIn <= maxAmountIn, "swap: transfer exceeds max");
// Compute output (floor)
amountOutUint = _internalToUintFloor(amountOutInternal, state.bases[outputTokenIndex]);
require(amountOutUint > 0, "swap: output zero");
}
/// @notice Internal quote for exact-input swap
function _quoteSwapExactIn_BalancedPair(
PoolLib.State storage state,
uint256 inputTokenIndex,
uint256 outputTokenIndex,
uint256 maxAmountIn,
int128 limitPrice,
uint256 swapFeePpm
)
internal
view
returns (
uint256 grossIn,
uint256 amountOutUint,
int128 amountInInternalUsed,
int128 amountOutInternal,
uint256 amountInUintNoFee,
uint256 feeUint
)
{
uint256 n = state.tokens.length;
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
require(maxAmountIn > 0, "swap: input zero");
require(state.lmsr.nAssets > 0, "swap: empty pool");
// Estimate max net input
(, uint256 netUintForSwap) = _computeFee(maxAmountIn, swapFeePpm);
// Convert to internal (floor)
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, state.bases[inputTokenIndex]);
require(deltaInternalI > int128(0), "swap: input too small after fee");
// Compute internal amounts using LMSR
(amountInInternalUsed, amountOutInternal) = LMSRStabilizedBalancedPair.swapAmountsForExactInput(
state.lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
// Convert actual used input internal -> uint (ceil) // Convert actual used input internal -> uint (ceil)
amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, state.bases[inputTokenIndex]); amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, state.bases[inputTokenIndex]);
@@ -805,7 +857,7 @@ library PoolLib {
/// @notice Internal quote for swap-to-limit /// @notice Internal quote for swap-to-limit
function _quoteSwapToLimit( function _quoteSwapToLimit(
State storage state, PoolLib.State storage state,
uint256 inputTokenIndex, uint256 inputTokenIndex,
uint256 outputTokenIndex, uint256 outputTokenIndex,
int128 limitPrice, int128 limitPrice,

139
src/PoolLibBalancedPair.sol Normal file
View File

@@ -0,0 +1,139 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30;
import "@abdk/ABDKMath64x64.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./LMSRStabilized.sol";
import "./LMSRStabilizedBalancedPair.sol";
import "./IPartyFlashCallback.sol";
import {PoolLib} from "./PoolLib.sol";
/// @title PoolLibBalancedPair - Library with optimized functions for stablecoin pair pools
/// @dev All functions are internal and accept State as the first parameter
library PoolLibBalancedPair {
using ABDKMath64x64 for int128;
using LMSRStabilized for LMSRStabilized.State;
using PoolLib for PoolLib.State;
using SafeERC20 for IERC20;
/// @notice Get swap amounts for exact input swap
function swapAmounts(
PoolLib.State storage state,
uint256 inputTokenIndex,
uint256 outputTokenIndex,
uint256 maxAmountIn,
int128 limitPrice,
uint256 swapFeePpm
) internal view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(
state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm
);
return (grossIn, outUint, feeUint);
}
/// @notice Internal quote for exact-input swap
function _quoteSwapExactIn(
PoolLib.State storage state,
uint256 inputTokenIndex,
uint256 outputTokenIndex,
uint256 maxAmountIn,
int128 limitPrice,
uint256 swapFeePpm
)
internal
view
returns (
uint256 grossIn,
uint256 amountOutUint,
int128 amountInInternalUsed,
int128 amountOutInternal,
uint256 amountInUintNoFee,
uint256 feeUint
)
{
uint256 n = state.tokens.length;
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
require(maxAmountIn > 0, "swap: input zero");
require(state.lmsr.nAssets > 0, "swap: empty pool");
// Estimate max net input
(, uint256 netUintForSwap) = PoolLib._computeFee(maxAmountIn, swapFeePpm);
// Convert to internal (floor)
int128 deltaInternalI = PoolLib._uintToInternalFloor(netUintForSwap, state.bases[inputTokenIndex]);
require(deltaInternalI > int128(0), "swap: input too small after fee");
// Compute internal amounts using LMSR
(amountInInternalUsed, amountOutInternal) = LMSRStabilizedBalancedPair.swapAmountsForExactInput(state.lmsr,
inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
// Convert actual used input internal -> uint (ceil)
amountInUintNoFee = PoolLib._internalToUintCeil(amountInInternalUsed, state.bases[inputTokenIndex]);
require(amountInUintNoFee > 0, "swap: input zero");
// Compute gross transfer including fee
feeUint = 0;
grossIn = amountInUintNoFee;
if (swapFeePpm > 0) {
feeUint = PoolLib._ceilFee(amountInUintNoFee, swapFeePpm);
grossIn += feeUint;
}
// Ensure within user max
require(grossIn <= maxAmountIn, "swap: transfer exceeds max");
// Compute output (floor)
amountOutUint = PoolLib._internalToUintFloor(amountOutInternal, state.bases[outputTokenIndex]);
require(amountOutUint > 0, "swap: output zero");
}
/// @notice Execute exact input swap
function swap(
PoolLib.State storage state,
address payer,
address receiver,
uint256 inputTokenIndex,
uint256 outputTokenIndex,
uint256 maxAmountIn,
int128 limitPrice,
uint256 deadline,
uint256 swapFeePpm
) internal returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
uint256 n = state.tokens.length;
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
require(maxAmountIn > 0, "swap: input zero");
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
// Read previous balances
uint256 prevBalI = IERC20(state.tokens[inputTokenIndex]).balanceOf(address(this));
uint256 prevBalJ = IERC20(state.tokens[outputTokenIndex]).balanceOf(address(this));
// Compute amounts
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) =
_quoteSwapExactIn(state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm);
// Transfer exact amount from payer
state.tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransferAmount);
uint256 balIAfter = IERC20(state.tokens[inputTokenIndex]).balanceOf(address(this));
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
// Transfer output to receiver
state.tokens[outputTokenIndex].safeTransfer(receiver, amountOutUint);
uint256 balJAfter = IERC20(state.tokens[outputTokenIndex]).balanceOf(address(this));
require(balJAfter == prevBalJ - amountOutUint, "swap: non-standard tokenOut");
// Update cached balances
state.cachedUintBalances[inputTokenIndex] = balIAfter;
state.cachedUintBalances[outputTokenIndex] = balJAfter;
// Apply swap to LMSR state
state.lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalUsed, amountOutInternal);
emit PoolLib.Swap(payer, receiver, state.tokens[inputTokenIndex], state.tokens[outputTokenIndex], totalTransferAmount, amountOutUint);
return (totalTransferAmount, amountOutUint, feeUint);
}
}

View File

@@ -1,14 +1,15 @@
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30; pragma solidity ^0.8.30;
import "forge-std/Test.sol"; import "../src/Deploy.sol";
import "@abdk/ABDKMath64x64.sol"; import "../src/IPartyFlashCallback.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../src/LMSRStabilized.sol"; import "../src/LMSRStabilized.sol";
import "../src/PartyPool.sol"; import "../src/PartyPool.sol";
import "@abdk/ABDKMath64x64.sol";
// Import the flash callback interface // Import the flash callback interface
import "../src/IPartyFlashCallback.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "forge-std/Test.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 IPartyFlashCallback { contract FlashBorrower is IPartyFlashCallback {
@@ -172,7 +173,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 = new PartyPool(poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, false); PartyPool newPool = Deploy.newPartyPool(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++) {
@@ -212,7 +213,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 = new PartyPool(poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, true); PartyPool newPool = Deploy.newPartyPool(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

@@ -1,14 +1,15 @@
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30; pragma solidity ^0.8.30;
import "forge-std/Test.sol"; import "../src/Deploy.sol";
import "@abdk/ABDKMath64x64.sol"; import "../src/IPartyFlashCallback.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../src/LMSRStabilized.sol"; import "../src/LMSRStabilized.sol";
import "../src/PartyPool.sol"; import "../src/PartyPool.sol";
import "@abdk/ABDKMath64x64.sol";
// Import the flash callback interface // Import the flash callback interface
import "../src/IPartyFlashCallback.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "forge-std/Test.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 IPartyFlashCallback { contract FlashBorrower is IPartyFlashCallback {
@@ -197,7 +198,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 = new PartyPool("LP", "LP", tokens, bases, kappa3, feePpm, feePpm, false); pool = Deploy.newPartyPool("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
@@ -227,7 +228,7 @@ contract PartyPoolTest is Test {
} }
int128 kappa10 = LMSRStabilized.computeKappaFromSlippage(tokens10.length, tradeFrac, targetSlippage); int128 kappa10 = LMSRStabilized.computeKappaFromSlippage(tokens10.length, tradeFrac, targetSlippage);
pool10 = new PartyPool("LP10", "LP10", tokens10, bases10, kappa10, feePpm, feePpm, false); pool10 = Deploy.newPartyPool("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);
@@ -1231,11 +1232,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 = new PartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault, feePpm, feePpm, false); PartyPool poolDefault = Deploy.newPartyPool("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 = new PartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom, feePpm, feePpm, false); PartyPool poolCustom = Deploy.newPartyPool("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);
@@ -1307,9 +1308,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 = new PartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault2, feePpm, feePpm, false); PartyPool poolDefault = Deploy.newPartyPool("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 = new PartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom2, feePpm, feePpm, false); PartyPool poolCustom = Deploy.newPartyPool("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);