Deploy; PartyPoolBalancedPair as separate contract
This commit is contained in:
@@ -9,7 +9,7 @@ remappings = [
|
||||
optimizer=true
|
||||
optimizer_runs=999999999
|
||||
viaIR=true
|
||||
gas_reports = ['PartyPool', 'PartyPlanner']
|
||||
gas_reports = ['PartyPlanner', 'PartyPoolBalancedPair', 'PartyPool', ]
|
||||
fs_permissions = [{ access = "write", path = "chain.json"}]
|
||||
|
||||
[lint]
|
||||
|
||||
30
src/Deploy.sol
Normal file
30
src/Deploy.sol
Normal 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_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,9 @@
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "./IPartyPlanner.sol";
|
||||
import "./PartyPool.sol";
|
||||
import "./LMSRStabilized.sol";
|
||||
import "./PartyPool.sol";
|
||||
import "./PartyPoolBalancedPair.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.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");
|
||||
|
||||
// Create a new PartyPool instance (kappa-based constructor)
|
||||
pool = new PartyPool(
|
||||
name_,
|
||||
symbol_,
|
||||
_tokens,
|
||||
_bases,
|
||||
_kappa,
|
||||
_swapFeePpm,
|
||||
_flashFeePpm,
|
||||
_stable
|
||||
);
|
||||
pool = _stable && _tokens.length == 2 ?
|
||||
new PartyPoolBalancedPair(name_, symbol_, _tokens, _bases, _kappa, _swapFeePpm, _flashFeePpm) :
|
||||
new PartyPool(name_, symbol_, _tokens, _bases, _kappa, _swapFeePpm, _flashFeePpm);
|
||||
|
||||
_allPools.push(pool);
|
||||
_poolSupported[pool] = true;
|
||||
|
||||
@@ -32,9 +32,6 @@ contract PartyPool is PoolBase, IPartyPool {
|
||||
/// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
|
||||
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
|
||||
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 swapFeePpm_ fee in parts-per-million, taken from swap input amounts before LMSR calculations
|
||||
/// @param flashFeePpm_ fee in parts-per-million, taken for flash loans
|
||||
/// @param stable_ if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated.
|
||||
constructor(
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
@@ -73,15 +69,13 @@ contract PartyPool is PoolBase, IPartyPool {
|
||||
uint256[] memory bases_,
|
||||
int128 kappa_,
|
||||
uint256 swapFeePpm_,
|
||||
uint256 flashFeePpm_,
|
||||
bool stable_
|
||||
uint256 flashFeePpm_
|
||||
) ERC20(name_, symbol_) {
|
||||
kappa = kappa_;
|
||||
require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm");
|
||||
swapFeePpm = swapFeePpm_;
|
||||
require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm");
|
||||
flashFeePpm = flashFeePpm_;
|
||||
_stablePair = stable_ && tokens_.length == 2;
|
||||
|
||||
// Initialize state using library
|
||||
s.initialize(tokens_, bases_);
|
||||
@@ -155,8 +149,8 @@ contract PartyPool is PoolBase, IPartyPool {
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
return s.swapAmounts(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm, _stablePair);
|
||||
) virtual external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
return s.swapAmounts(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm);
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
@@ -176,17 +170,7 @@ contract PartyPool is PoolBase, IPartyPool {
|
||||
return s.swapMintAmounts(inputTokenIndex, maxAmountIn, swapFeePpm, totalSupply());
|
||||
}
|
||||
|
||||
/// @notice Swap input token i -> token j. Payer must approve token i.
|
||||
/// @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)
|
||||
/// @inheritdoc IPartyPool
|
||||
function swap(
|
||||
address payer,
|
||||
address receiver,
|
||||
@@ -195,8 +179,8 @@ contract PartyPool is PoolBase, IPartyPool {
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline
|
||||
) external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
return s.swap(payer, receiver, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, deadline, swapFeePpm, _stablePair);
|
||||
) virtual external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
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.
|
||||
|
||||
42
src/PartyPoolBalancedPair.sol
Normal file
42
src/PartyPoolBalancedPair.sol
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
114
src/PoolLib.sol
114
src/PoolLib.sol
@@ -51,7 +51,7 @@ library PoolLib {
|
||||
/// @param tokens_ Array of token addresses
|
||||
/// @param bases_ Array of base denominators for each token
|
||||
function initialize(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
IERC20[] memory tokens_,
|
||||
uint256[] memory bases_
|
||||
) internal {
|
||||
@@ -78,7 +78,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Get deposit amounts needed for minting LP tokens
|
||||
function mintDepositAmounts(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 lpTokenAmount,
|
||||
uint256 totalSupply
|
||||
) internal view returns (uint256[] memory depositAmounts) {
|
||||
@@ -102,7 +102,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Initial mint to set up pool for the first time
|
||||
function initialMint(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address receiver,
|
||||
uint256 lpTokens,
|
||||
int128 kappa,
|
||||
@@ -142,7 +142,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Proportional mint for existing pool
|
||||
function mint(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 lpTokenAmount,
|
||||
@@ -209,7 +209,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Get withdrawal amounts for burning LP tokens
|
||||
function burnReceiveAmounts(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 lpTokenAmount,
|
||||
uint256 totalSupply
|
||||
) internal view returns (uint256[] memory withdrawAmounts) {
|
||||
@@ -230,7 +230,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Burn LP tokens and withdraw proportional basket
|
||||
function burn(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 lpAmount,
|
||||
@@ -293,23 +293,22 @@ library PoolLib {
|
||||
|
||||
/// @notice Get swap amounts for exact input swap
|
||||
function swapAmounts(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 swapFeePpm,
|
||||
bool stablePair
|
||||
uint256 swapFeePpm
|
||||
) internal view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(
|
||||
state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm, stablePair
|
||||
state, inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice, swapFeePpm
|
||||
);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
|
||||
/// @notice Get swap amounts for swap to price limit
|
||||
function swapToLimitAmounts(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
@@ -323,7 +322,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Get amounts for swapMint operation
|
||||
function swapMintAmounts(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
uint256 swapFeePpm,
|
||||
@@ -381,7 +380,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Execute exact input swap
|
||||
function swap(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
@@ -389,8 +388,7 @@ library PoolLib {
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
uint256 swapFeePpm,
|
||||
bool stablePair
|
||||
uint256 swapFeePpm
|
||||
) internal returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
uint256 n = state.tokens.length;
|
||||
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
|
||||
@@ -403,7 +401,7 @@ library PoolLib {
|
||||
|
||||
// Compute amounts
|
||||
(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
|
||||
state.tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransferAmount);
|
||||
@@ -429,7 +427,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Execute swap to price limit
|
||||
function swapToLimit(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
@@ -475,7 +473,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Get amounts for burnSwap operation
|
||||
function burnSwapAmounts(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 lpAmount,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 swapFeePpm,
|
||||
@@ -508,7 +506,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Single-token mint (swapMint)
|
||||
function swapMint(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
@@ -557,7 +555,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Burn LP tokens and swap to single asset (burnSwap)
|
||||
function burnSwap(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 lpAmount,
|
||||
@@ -607,7 +605,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Calculate flash loan repayment amounts
|
||||
function flashRepaymentAmounts(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256[] memory loanAmounts,
|
||||
uint256 flashFeePpm
|
||||
) internal view returns (uint256[] memory repaymentAmounts) {
|
||||
@@ -622,7 +620,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Execute flash loan
|
||||
function flash(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
address recipient,
|
||||
uint256[] memory amounts,
|
||||
bytes calldata data,
|
||||
@@ -683,7 +681,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Get marginal price between two tokens
|
||||
function price(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 baseTokenIndex,
|
||||
uint256 quoteTokenIndex
|
||||
) internal view returns (int128) {
|
||||
@@ -695,7 +693,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Get price of one LP token in quote asset
|
||||
function poolPrice(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 quoteTokenIndex,
|
||||
uint256 totalSupply
|
||||
) internal view returns (int128) {
|
||||
@@ -747,13 +745,12 @@ library PoolLib {
|
||||
|
||||
/// @notice Internal quote for exact-input swap
|
||||
function _quoteSwapExactIn(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 swapFeePpm,
|
||||
bool stablePair
|
||||
uint256 swapFeePpm
|
||||
)
|
||||
internal
|
||||
view
|
||||
@@ -779,9 +776,64 @@ library PoolLib {
|
||||
require(deltaInternalI > int128(0), "swap: input too small after fee");
|
||||
|
||||
// Compute internal amounts using LMSR
|
||||
(amountInInternalUsed, amountOutInternal) =
|
||||
stablePair ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(state.lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice)
|
||||
: state.lmsr.swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
|
||||
(amountInInternalUsed, amountOutInternal) = 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)
|
||||
amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, state.bases[inputTokenIndex]);
|
||||
@@ -805,7 +857,7 @@ library PoolLib {
|
||||
|
||||
/// @notice Internal quote for swap-to-limit
|
||||
function _quoteSwapToLimit(
|
||||
State storage state,
|
||||
PoolLib.State storage state,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
|
||||
139
src/PoolLibBalancedPair.sol
Normal file
139
src/PoolLibBalancedPair.sol
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "../src/Deploy.sol";
|
||||
import "../src/IPartyFlashCallback.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
for (uint256 i = 0; i < numTokens; i++) {
|
||||
@@ -212,7 +213,7 @@ contract GasTest is Test {
|
||||
ierc20Tokens[i] = IERC20(tokens[i]);
|
||||
}
|
||||
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
|
||||
for (uint256 i = 0; i < numTokens; i++) {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "../src/Deploy.sol";
|
||||
import "../src/IPartyFlashCallback.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
|
||||
// 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
|
||||
contract FlashBorrower is IPartyFlashCallback {
|
||||
@@ -197,7 +198,7 @@ contract PartyPoolTest is Test {
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
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)
|
||||
// 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);
|
||||
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
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
@@ -1231,11 +1232,11 @@ contract PartyPoolTest is Test {
|
||||
|
||||
// Pool with default initialization (lpTokens = 0)
|
||||
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)
|
||||
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
|
||||
token0.mint(address(this), INIT_BAL * 2);
|
||||
@@ -1307,9 +1308,9 @@ contract PartyPoolTest is Test {
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
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);
|
||||
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
|
||||
token0.mint(address(this), INIT_BAL * 4);
|
||||
|
||||
Reference in New Issue
Block a user