swapMintImpl moved into mintImpl
This commit is contained in:
@@ -15,6 +15,7 @@ import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
|||||||
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
||||||
import {ERC20External} from "./ERC20External.sol";
|
import {ERC20External} from "./ERC20External.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
||||||
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
||||||
/// The pool issues an ERC20 LP token representing proportional ownership.
|
/// The pool issues an ERC20 LP token representing proportional ownership.
|
||||||
@@ -250,13 +251,6 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Helper to record cached balances as effectiveBalance = onchain - owed. Reverts if owed > onchain.
|
|
||||||
function _recordCachedBalance(uint256 idx, uint256 onchainBal) internal {
|
|
||||||
uint256 owed = protocolFeesOwed[idx];
|
|
||||||
require(onchainBal >= owed, "balance < protocol owed");
|
|
||||||
cachedUintBalances[idx] = onchainBal - owed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Swap input token i -> token j. Payer must approve token i.
|
/// @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.
|
/// @dev This function transfers the exact gross input (including fee) from payer and sends the computed output to receiver.
|
||||||
/// Non-standard tokens (fee-on-transfer, rebasers) are rejected via balance checks.
|
/// Non-standard tokens (fee-on-transfer, rebasers) are rejected via balance checks.
|
||||||
@@ -499,7 +493,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
|||||||
|
|
||||||
function swapMintAmounts(uint256 inputTokenIndex, uint256 maxAmountIn) external view
|
function swapMintAmounts(uint256 inputTokenIndex, uint256 maxAmountIn) external view
|
||||||
returns (uint256 amountInUsed, uint256 fee, uint256 lpMinted) {
|
returns (uint256 amountInUsed, uint256 fee, uint256 lpMinted) {
|
||||||
return SWAP_MINT_IMPL.swapMintAmounts(
|
return MINT_IMPL.swapMintAmounts(
|
||||||
inputTokenIndex,
|
inputTokenIndex,
|
||||||
maxAmountIn,
|
maxAmountIn,
|
||||||
SWAP_FEE_PPM,
|
SWAP_FEE_PPM,
|
||||||
@@ -511,7 +505,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
|||||||
|
|
||||||
function burnSwapAmounts(uint256 lpAmount, uint256 inputTokenIndex) external view
|
function burnSwapAmounts(uint256 lpAmount, uint256 inputTokenIndex) external view
|
||||||
returns (uint256 amountOut) {
|
returns (uint256 amountOut) {
|
||||||
return SWAP_MINT_IMPL.burnSwapAmounts(
|
return MINT_IMPL.burnSwapAmounts(
|
||||||
lpAmount,
|
lpAmount,
|
||||||
inputTokenIndex,
|
inputTokenIndex,
|
||||||
SWAP_FEE_PPM,
|
SWAP_FEE_PPM,
|
||||||
@@ -537,32 +531,18 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
|||||||
uint256 deadline
|
uint256 deadline
|
||||||
) external returns (uint256 lpMinted) {
|
) external returns (uint256 lpMinted) {
|
||||||
bytes memory data = abi.encodeWithSignature(
|
bytes memory data = abi.encodeWithSignature(
|
||||||
"swapMint(address,address,uint256,uint256,uint256,uint256)",
|
"swapMint(address,address,uint256,uint256,uint256,uint256,uint256)",
|
||||||
payer,
|
payer,
|
||||||
receiver,
|
receiver,
|
||||||
inputTokenIndex,
|
inputTokenIndex,
|
||||||
maxAmountIn,
|
maxAmountIn,
|
||||||
deadline,
|
deadline,
|
||||||
SWAP_FEE_PPM
|
SWAP_FEE_PPM,
|
||||||
|
PROTOCOL_FEE_PPM
|
||||||
);
|
);
|
||||||
|
|
||||||
bytes memory result = Address.functionDelegateCall(address(SWAP_MINT_IMPL), data);
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
||||||
// New ABI: implementation returns (uint256 lpMinted, uint256 feeUintActual)
|
return abi.decode(result, (uint256));
|
||||||
(uint256 lpOut, uint256 feeUintActual) = abi.decode(result, (uint256, uint256));
|
|
||||||
|
|
||||||
// Accrue protocol share (floor) from the fee on the input token
|
|
||||||
if (PROTOCOL_FEE_PPM > 0 && feeUintActual > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
|
|
||||||
uint256 protoShare = (feeUintActual * PROTOCOL_FEE_PPM) / 1_000_000;
|
|
||||||
if (protoShare > 0) {
|
|
||||||
protocolFeesOwed[inputTokenIndex] += protoShare;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update cached balance for the input token to effective onchain - owed
|
|
||||||
uint256 bal = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
||||||
_recordCachedBalance(inputTokenIndex, bal);
|
|
||||||
|
|
||||||
return lpOut;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
|
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
|
||||||
@@ -581,32 +561,18 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
|||||||
uint256 deadline
|
uint256 deadline
|
||||||
) external returns (uint256 amountOutUint) {
|
) external returns (uint256 amountOutUint) {
|
||||||
bytes memory data = abi.encodeWithSignature(
|
bytes memory data = abi.encodeWithSignature(
|
||||||
"burnSwap(address,address,uint256,uint256,uint256,uint256)",
|
"burnSwap(address,address,uint256,uint256,uint256,uint256,uint256)",
|
||||||
payer,
|
payer,
|
||||||
receiver,
|
receiver,
|
||||||
lpAmount,
|
lpAmount,
|
||||||
inputTokenIndex,
|
inputTokenIndex,
|
||||||
deadline,
|
deadline,
|
||||||
SWAP_FEE_PPM
|
SWAP_FEE_PPM,
|
||||||
|
PROTOCOL_FEE_PPM
|
||||||
);
|
);
|
||||||
|
|
||||||
bytes memory result = Address.functionDelegateCall(address(SWAP_MINT_IMPL), data);
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
||||||
// New ABI: implementation returns (uint256 amountOutUint, uint256 feeTokenUint)
|
return abi.decode(result, (uint256));
|
||||||
(uint256 outAmt, uint256 feeTokenUint) = abi.decode(result, (uint256, uint256));
|
|
||||||
|
|
||||||
// Accrue protocol share (floor) from the token-side fee computed by implementation
|
|
||||||
if (PROTOCOL_FEE_PPM > 0 && feeTokenUint > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
|
|
||||||
uint256 protoShare = (feeTokenUint * PROTOCOL_FEE_PPM) / 1_000_000;
|
|
||||||
if (protoShare > 0) {
|
|
||||||
protocolFeesOwed[inputTokenIndex] += protoShare;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update cached balance for the target token to effective onchain - owed
|
|
||||||
uint256 bal = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
||||||
_recordCachedBalance(inputTokenIndex, bal);
|
|
||||||
|
|
||||||
return outAmt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -116,4 +116,12 @@ abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard {
|
|||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Helper to record cached balances as effectiveBalance = onchain - owed. Reverts if owed > onchain.
|
||||||
|
function _recordCachedBalance(uint256 idx, uint256 onchainBal) internal {
|
||||||
|
uint256 owed = protocolFeesOwed[idx];
|
||||||
|
require(onchainBal >= owed, "balance < protocol owed");
|
||||||
|
cachedUintBalances[idx] = onchainBal - owed;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// SPDX-License-Identifier: UNLICENSED
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
pragma solidity ^0.8.30;
|
pragma solidity ^0.8.30;
|
||||||
|
|
||||||
import "@abdk/ABDKMath64x64.sol";
|
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
import "./PartyPoolBase.sol";
|
import {ERC20Internal} from "./ERC20Internal.sol";
|
||||||
import "./LMSRStabilized.sol";
|
import {IPartyPool} from "./IPartyPool.sol";
|
||||||
import {PartyPool} from "./PartyPool.sol";
|
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||||
|
import {PartyPoolBase} from "./PartyPoolBase.sol";
|
||||||
|
|
||||||
/// @title PartyPoolMintImpl - Implementation contract for mint and burn functions
|
/// @title PartyPoolMintImpl - Implementation contract for mint and burn functions
|
||||||
/// @notice This contract contains the mint and burn implementation that will be called via delegatecall
|
/// @notice This contract contains the mint and burn implementation that will be called via delegatecall
|
||||||
@@ -20,6 +21,10 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
||||||
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialization Mint
|
||||||
|
//
|
||||||
|
|
||||||
function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external
|
function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external
|
||||||
returns (uint256 lpMinted) {
|
returns (uint256 lpMinted) {
|
||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
@@ -55,6 +60,11 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
emit Mint(address(0), receiver, depositAmounts, lpMinted);
|
emit Mint(address(0), receiver, depositAmounts, lpMinted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Regular Mint and Burn
|
||||||
|
//
|
||||||
|
|
||||||
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external returns (uint256 lpMinted) {
|
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external returns (uint256 lpMinted) {
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
@@ -192,7 +202,8 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
emit Burn(payer, receiver, withdrawAmounts, lpAmount);
|
emit Burn(payer, receiver, withdrawAmounts, lpAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mintAmounts(uint256 lpTokenAmount, uint256 numAssets, uint256 totalSupply, uint256[] memory cachedUintBalances) public pure
|
function mintAmounts(uint256 lpTokenAmount,
|
||||||
|
uint256 numAssets, uint256 totalSupply, uint256[] memory cachedUintBalances) public pure
|
||||||
returns (uint256[] memory depositAmounts) {
|
returns (uint256[] memory depositAmounts) {
|
||||||
depositAmounts = new uint256[](numAssets);
|
depositAmounts = new uint256[](numAssets);
|
||||||
|
|
||||||
@@ -232,4 +243,357 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
|
|
||||||
return withdrawAmounts;
|
return withdrawAmounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Swap-Mint and Burn-Swap
|
||||||
|
//
|
||||||
|
|
||||||
|
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
||||||
|
/// @dev swapMint executes as an exact-in planned swap followed by proportional scaling of qInternal.
|
||||||
|
/// The function emits SwapMint (gross, net, fee) and also emits Mint for LP issuance.
|
||||||
|
/// @param payer who transfers the input token
|
||||||
|
/// @param receiver who receives the minted LP tokens
|
||||||
|
/// @param inputTokenIndex index of the input token
|
||||||
|
/// @param maxAmountIn maximum uint token input (inclusive of fee)
|
||||||
|
/// @param deadline optional deadline
|
||||||
|
/// @param swapFeePpm fee in parts-per-million for this pool
|
||||||
|
/// @return lpMinted actual LP minted (uint)
|
||||||
|
function swapMint(
|
||||||
|
address payer,
|
||||||
|
address receiver,
|
||||||
|
uint256 inputTokenIndex,
|
||||||
|
uint256 maxAmountIn,
|
||||||
|
uint256 deadline,
|
||||||
|
uint256 swapFeePpm,
|
||||||
|
uint256 protocolFeePpm
|
||||||
|
) external returns (uint256 lpMinted) {
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
require(inputTokenIndex < n, "swapMint: idx");
|
||||||
|
require(maxAmountIn > 0, "swapMint: input zero");
|
||||||
|
require(deadline == 0 || block.timestamp <= deadline, "swapMint: deadline");
|
||||||
|
require(lmsr.nAssets > 0, "swapMint: uninit pool");
|
||||||
|
|
||||||
|
// compute fee on gross maxAmountIn to get an initial net estimate (we'll recompute based on actual used)
|
||||||
|
(, uint256 netUintGuess) = _computeFee(maxAmountIn, swapFeePpm);
|
||||||
|
|
||||||
|
// Convert the net guess to internal (floor)
|
||||||
|
int128 netInternalGuess = _uintToInternalFloor(netUintGuess, bases[inputTokenIndex]);
|
||||||
|
require(netInternalGuess > int128(0), "swapMint: input too small after fee");
|
||||||
|
|
||||||
|
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
|
||||||
|
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) = lmsr.swapAmountsForMint(inputTokenIndex, netInternalGuess);
|
||||||
|
|
||||||
|
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
|
||||||
|
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, bases[inputTokenIndex]);
|
||||||
|
require(amountInUint > 0, "swapMint: input zero after internal conversion");
|
||||||
|
|
||||||
|
// Compute fee on the actual used input and total transfer amount (ceiling)
|
||||||
|
uint256 feeUintActual = _ceilFee(amountInUint, swapFeePpm);
|
||||||
|
uint256 totalTransfer = amountInUint + feeUintActual;
|
||||||
|
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max");
|
||||||
|
|
||||||
|
// Record pre-balance and transfer tokens from payer, require exact receipt (revert on fee-on-transfer)
|
||||||
|
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||||
|
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransfer);
|
||||||
|
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||||
|
require(balIAfter == prevBalI + totalTransfer, "swapMint: non-standard tokenIn");
|
||||||
|
|
||||||
|
// Accrue protocol share (floor) from the fee on the input token
|
||||||
|
if (protocolFeePpm > 0 && feeUintActual > 0) {
|
||||||
|
uint256 protoShare = (feeUintActual * protocolFeePpm) / 1_000_000;
|
||||||
|
if (protoShare > 0) {
|
||||||
|
protocolFeesOwed[inputTokenIndex] += protoShare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update cached balance for the input token to effective onchain - owed
|
||||||
|
_recordCachedBalance(inputTokenIndex, balIAfter);
|
||||||
|
|
||||||
|
// Compute old and new scaled size metrics to determine LP minted
|
||||||
|
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
||||||
|
require(oldTotal > int128(0), "swapMint: zero total");
|
||||||
|
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||||
|
|
||||||
|
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
|
||||||
|
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||||
|
|
||||||
|
uint256 actualLpToMint;
|
||||||
|
// Use natural ERC20 function since base contract inherits from ERC20
|
||||||
|
uint256 currentSupply = _totalSupply;
|
||||||
|
if (currentSupply == 0) {
|
||||||
|
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
||||||
|
actualLpToMint = newScaled;
|
||||||
|
} else {
|
||||||
|
require(oldScaled > 0, "swapMint: oldScaled zero");
|
||||||
|
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||||
|
if (delta > 0) {
|
||||||
|
// floor truncation rounds in favor of pool
|
||||||
|
actualLpToMint = (currentSupply * delta) / oldScaled;
|
||||||
|
} else {
|
||||||
|
actualLpToMint = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require(actualLpToMint > 0, "swapMint: zero LP minted");
|
||||||
|
|
||||||
|
// Update LMSR internal state: scale qInternal proportionally by newTotal/oldTotal
|
||||||
|
int128[] memory newQInternal = new int128[](n);
|
||||||
|
for (uint256 idx = 0; idx < n; idx++) {
|
||||||
|
// newQInternal[idx] = qInternal[idx] * (newTotal / oldTotal)
|
||||||
|
newQInternal[idx] = lmsr.qInternal[idx].mul(newTotal).div(oldTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cached internal and kappa via updateForProportionalChange
|
||||||
|
lmsr.updateForProportionalChange(newQInternal);
|
||||||
|
|
||||||
|
// Use natural ERC20 function since base contract inherits from ERC20
|
||||||
|
_mint(receiver, actualLpToMint);
|
||||||
|
|
||||||
|
// Emit SwapMint event with gross transfer, net input and fee (planned exact-in)
|
||||||
|
emit IPartyPool.SwapMint(payer, receiver, inputTokenIndex, totalTransfer, amountInUint, feeUintActual);
|
||||||
|
|
||||||
|
// Emit standard Mint event which records deposit amounts and LP minted
|
||||||
|
emit IPartyPool.Mint(payer, receiver, new uint256[](n), actualLpToMint);
|
||||||
|
// Note: depositAmounts array omitted (empty) since swapMint uses single-token input
|
||||||
|
|
||||||
|
return actualLpToMint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Calculate the amounts for a swap mint operation
|
||||||
|
/// @dev This is a pure view function that computes swap mint amounts from provided state
|
||||||
|
/// @param inputTokenIndex index of the input token
|
||||||
|
/// @param maxAmountIn maximum amount of token to deposit (inclusive of fee)
|
||||||
|
/// @param swapFeePpm fee in parts-per-million
|
||||||
|
/// @param lmsrState current LMSR state
|
||||||
|
/// @param bases_ scaling bases for each token
|
||||||
|
/// @param totalSupply_ current total LP token supply
|
||||||
|
/// @return amountInUsed actual input amount used (excluding fee)
|
||||||
|
/// @return fee fee amount charged
|
||||||
|
/// @return lpMinted LP tokens that would be minted
|
||||||
|
function swapMintAmounts(
|
||||||
|
uint256 inputTokenIndex,
|
||||||
|
uint256 maxAmountIn,
|
||||||
|
uint256 swapFeePpm,
|
||||||
|
LMSRStabilized.State memory lmsrState,
|
||||||
|
uint256[] memory bases_,
|
||||||
|
uint256 totalSupply_
|
||||||
|
) public pure returns (uint256 amountInUsed, uint256 fee, uint256 lpMinted) {
|
||||||
|
require(inputTokenIndex < bases_.length, "swapMintAmounts: idx");
|
||||||
|
require(maxAmountIn > 0, "swapMintAmounts: input zero");
|
||||||
|
require(lmsrState.nAssets > 0, "swapMintAmounts: uninit pool");
|
||||||
|
|
||||||
|
// Compute fee on gross maxAmountIn to get an initial net estimate
|
||||||
|
uint256 feeGuess = 0;
|
||||||
|
uint256 netUintGuess = maxAmountIn;
|
||||||
|
if (swapFeePpm > 0) {
|
||||||
|
feeGuess = (maxAmountIn * swapFeePpm + 999999) / 1000000; // ceil fee
|
||||||
|
netUintGuess = maxAmountIn - feeGuess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the net guess to internal (floor)
|
||||||
|
int128 netInternalGuess = _uintToInternalFloorPure(netUintGuess, bases_[inputTokenIndex]);
|
||||||
|
require(netInternalGuess > int128(0), "swapMintAmounts: input too small after fee");
|
||||||
|
|
||||||
|
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
|
||||||
|
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) =
|
||||||
|
LMSRStabilized.swapAmountsForMint(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
||||||
|
inputTokenIndex, netInternalGuess);
|
||||||
|
|
||||||
|
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
|
||||||
|
amountInUsed = _internalToUintCeilPure(amountInInternalUsed, bases_[inputTokenIndex]);
|
||||||
|
require(amountInUsed > 0, "swapMintAmounts: input zero after internal conversion");
|
||||||
|
|
||||||
|
// Compute fee on the actual used input (ceiling)
|
||||||
|
fee = 0;
|
||||||
|
if (swapFeePpm > 0) {
|
||||||
|
fee = (amountInUsed * swapFeePpm + 999999) / 1000000; // ceil fee
|
||||||
|
}
|
||||||
|
uint256 totalTransfer = amountInUsed + fee;
|
||||||
|
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMintAmounts: transfer exceeds max");
|
||||||
|
|
||||||
|
// Compute old and new scaled size metrics to determine LP minted
|
||||||
|
int128 oldTotal = _computeSizeMetricPure(lmsrState.qInternal);
|
||||||
|
require(oldTotal > int128(0), "swapMintAmounts: zero total");
|
||||||
|
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||||
|
|
||||||
|
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
|
||||||
|
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||||
|
|
||||||
|
if (totalSupply_ == 0) {
|
||||||
|
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
||||||
|
lpMinted = newScaled;
|
||||||
|
} else {
|
||||||
|
require(oldScaled > 0, "swapMintAmounts: oldScaled zero");
|
||||||
|
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||||
|
if (delta > 0) {
|
||||||
|
// floor truncation rounds in favor of pool
|
||||||
|
lpMinted = (totalSupply_ * delta) / oldScaled;
|
||||||
|
} else {
|
||||||
|
lpMinted = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require(lpMinted > 0, "swapMintAmounts: zero LP minted");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Calculate the amounts for a burn swap operation
|
||||||
|
/// @dev This is a pure view function that computes burn swap amounts from provided state
|
||||||
|
/// @param lpAmount amount of LP tokens to burn
|
||||||
|
/// @param inputTokenIndex index of target asset to receive
|
||||||
|
/// @param swapFeePpm fee in parts-per-million
|
||||||
|
/// @param lmsrState current LMSR state
|
||||||
|
/// @param bases_ scaling bases for each token
|
||||||
|
/// @param totalSupply_ current total LP token supply
|
||||||
|
/// @return amountOut amount of target asset that would be received
|
||||||
|
function burnSwapAmounts(
|
||||||
|
uint256 lpAmount,
|
||||||
|
uint256 inputTokenIndex,
|
||||||
|
uint256 swapFeePpm,
|
||||||
|
LMSRStabilized.State memory lmsrState,
|
||||||
|
uint256[] memory bases_,
|
||||||
|
uint256 totalSupply_
|
||||||
|
) public pure returns (uint256 amountOut) {
|
||||||
|
require(inputTokenIndex < bases_.length, "burnSwapAmounts: idx");
|
||||||
|
require(lpAmount > 0, "burnSwapAmounts: zero lp");
|
||||||
|
require(totalSupply_ > 0, "burnSwapAmounts: empty supply");
|
||||||
|
|
||||||
|
// alpha = lpAmount / supply as Q64.64
|
||||||
|
int128 alpha = ABDKMath64x64.divu(lpAmount, totalSupply_) // fraction of total supply to burn
|
||||||
|
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
|
||||||
|
|
||||||
|
// Use LMSR view to compute single-asset payout and burned size-metric
|
||||||
|
(int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
||||||
|
inputTokenIndex, alpha);
|
||||||
|
|
||||||
|
// Convert payoutInternal -> uint (floor) to favor pool
|
||||||
|
amountOut = _internalToUintFloorPure(payoutInternal, bases_[inputTokenIndex]);
|
||||||
|
require(amountOut > 0, "burnSwapAmounts: output zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
|
||||||
|
/// @dev The function burns LP tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state.
|
||||||
|
/// @param payer who burns LP tokens
|
||||||
|
/// @param receiver who receives the single asset
|
||||||
|
/// @param lpAmount amount of LP tokens to burn
|
||||||
|
/// @param inputTokenIndex index of target asset to receive
|
||||||
|
/// @param deadline optional deadline
|
||||||
|
/// @param swapFeePpm fee in parts-per-million for this pool (may be used for future fee logic)
|
||||||
|
/// @return amountOutUint uint amount of asset i sent to receiver
|
||||||
|
function burnSwap(
|
||||||
|
address payer,
|
||||||
|
address receiver,
|
||||||
|
uint256 lpAmount,
|
||||||
|
uint256 inputTokenIndex,
|
||||||
|
uint256 deadline,
|
||||||
|
uint256 swapFeePpm,
|
||||||
|
uint256 protocolFeePpm
|
||||||
|
) external returns (uint256 amountOutUint) {
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
require(inputTokenIndex < n, "burnSwap: idx");
|
||||||
|
require(lpAmount > 0, "burnSwap: zero lp");
|
||||||
|
require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline");
|
||||||
|
|
||||||
|
uint256 supply = _totalSupply;
|
||||||
|
require(supply > 0, "burnSwap: empty supply");
|
||||||
|
require(_balances[payer] >= lpAmount, "burnSwap: insufficient LP");
|
||||||
|
|
||||||
|
// alpha = lpAmount / supply as Q64.64 (adjusted for fee)
|
||||||
|
int128 alpha = ABDKMath64x64.divu(lpAmount, supply) // fraction of total supply to burn
|
||||||
|
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
|
||||||
|
|
||||||
|
// Use LMSR view to compute single-asset payout and burned size-metric
|
||||||
|
(int128 payoutInternal, ) = lmsr.swapAmountsForBurn(inputTokenIndex, alpha);
|
||||||
|
|
||||||
|
// Convert payoutInternal -> uint (floor) to favor pool
|
||||||
|
amountOutUint = _internalToUintFloor(payoutInternal, bases[inputTokenIndex]);
|
||||||
|
require(amountOutUint > 0, "burnSwap: output zero");
|
||||||
|
|
||||||
|
// Compute gross payout (no swap fee) so we can determine token-side fee = gross - net
|
||||||
|
int128 alphaGross = ABDKMath64x64.divu(lpAmount, supply); // gross fraction (no swap fee)
|
||||||
|
(int128 payoutGrossInternal, ) = lmsr.swapAmountsForBurn(inputTokenIndex, alphaGross);
|
||||||
|
uint256 payoutGrossUint = _internalToUintFloor(payoutGrossInternal, bases[inputTokenIndex]);
|
||||||
|
uint256 feeTokenUint = (payoutGrossUint > amountOutUint) ? (payoutGrossUint - amountOutUint) : 0;
|
||||||
|
|
||||||
|
// Accrue protocol share (floor) from the token-side fee
|
||||||
|
if (protocolFeePpm > 0 && feeTokenUint > 0) {
|
||||||
|
uint256 protoShare = (feeTokenUint * protocolFeePpm) / 1_000_000;
|
||||||
|
if (protoShare > 0) {
|
||||||
|
protocolFeesOwed[inputTokenIndex] += protoShare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer the payout to receiver
|
||||||
|
tokens[inputTokenIndex].safeTransfer(receiver, amountOutUint);
|
||||||
|
|
||||||
|
// Burn LP tokens from payer (authorization via allowance)
|
||||||
|
if (msg.sender != payer) {
|
||||||
|
uint256 allowed = _allowances[payer][msg.sender];
|
||||||
|
require(allowed >= lpAmount, "burnSwap: allowance insufficient");
|
||||||
|
_approve(payer, msg.sender, allowed - lpAmount);
|
||||||
|
}
|
||||||
|
_burn(payer, lpAmount);
|
||||||
|
|
||||||
|
// Update cached balances by reading on-chain balances for all tokens
|
||||||
|
int128[] memory newQInternal = new int128[](n);
|
||||||
|
for (uint256 idx = 0; idx < n; idx++) {
|
||||||
|
uint256 bal = IERC20(tokens[idx]).balanceOf(address(this));
|
||||||
|
cachedUintBalances[idx] = bal;
|
||||||
|
_recordCachedBalance(inputTokenIndex, bal);
|
||||||
|
newQInternal[idx] = _uintToInternalFloor(bal, bases[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit BurnSwap with public-facing info only (do not expose ΔS or LP burned)
|
||||||
|
emit IPartyPool.BurnSwap(payer, receiver, inputTokenIndex, amountOutUint);
|
||||||
|
|
||||||
|
// If entire pool drained, deinit; else update proportionally
|
||||||
|
bool allZero = true;
|
||||||
|
for (uint256 idx = 0; idx < n; idx++) {
|
||||||
|
if (newQInternal[idx] != int128(0)) { allZero = false; break; }
|
||||||
|
}
|
||||||
|
if (allZero) {
|
||||||
|
lmsr.deinit();
|
||||||
|
} else {
|
||||||
|
lmsr.updateForProportionalChange(newQInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit IPartyPool.Burn(payer, receiver, new uint256[](n), lpAmount);
|
||||||
|
return amountOutUint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Pure version of _uintToInternalFloor for use in view functions
|
||||||
|
function _uintToInternalFloorPure(uint256 amount, uint256 base) internal pure returns (int128) {
|
||||||
|
// amount / base as Q64.64, floored
|
||||||
|
return ABDKMath64x64.divu(amount, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Pure version of _internalToUintCeil for use in view functions
|
||||||
|
function _internalToUintCeilPure(int128 amount, uint256 base) internal pure returns (uint256) {
|
||||||
|
// Convert Q64.64 to uint with ceiling: ceil(amount * base)
|
||||||
|
// Use mulu which floors, then add remainder check for ceiling
|
||||||
|
uint256 floored = ABDKMath64x64.mulu(amount, base);
|
||||||
|
// Check if there's a fractional part by computing amount * base - floored
|
||||||
|
int128 baseQ64 = ABDKMath64x64.fromUInt(base);
|
||||||
|
int128 flooredQ64 = ABDKMath64x64.fromUInt(floored);
|
||||||
|
int128 product = amount.mul(baseQ64);
|
||||||
|
if (product > flooredQ64) {
|
||||||
|
return floored + 1; // Ceiling
|
||||||
|
}
|
||||||
|
return floored;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Pure version of _internalToUintFloor for use in view functions
|
||||||
|
function _internalToUintFloorPure(int128 amount, uint256 base) internal pure returns (uint256) {
|
||||||
|
// Convert Q64.64 to uint with floor: floor(amount * base)
|
||||||
|
return ABDKMath64x64.mulu(amount, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Pure version of _computeSizeMetric for use in view functions
|
||||||
|
function _computeSizeMetricPure(int128[] memory qInternal) internal pure returns (int128) {
|
||||||
|
int128 sum = int128(0);
|
||||||
|
for (uint256 i = 0; i < qInternal.length; i++) {
|
||||||
|
sum = sum.add(qInternal[i]);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|||||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
import "./PartyPoolBase.sol";
|
import "./PartyPoolBase.sol";
|
||||||
import "./LMSRStabilized.sol";
|
import "./LMSRStabilized.sol";
|
||||||
|
import {IPartyPool} from "./IPartyPool.sol";
|
||||||
|
|
||||||
/// @title PartyPoolSwapMintImpl - Implementation contract for swapMint and burnSwap functions
|
/// @title PartyPoolSwapMintImpl - Implementation contract for swapMint and burnSwap functions
|
||||||
/// @notice This contract contains the swapMint and burnSwap implementation that will be called via delegatecall
|
/// @notice This contract contains the swapMint and burnSwap implementation that will be called via delegatecall
|
||||||
@@ -15,341 +16,5 @@ contract PartyPoolSwapMintImpl is PartyPoolBase {
|
|||||||
using LMSRStabilized for LMSRStabilized.State;
|
using LMSRStabilized for LMSRStabilized.State;
|
||||||
using SafeERC20 for IERC20;
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
// Events that mirror the main contract events
|
|
||||||
event SwapMint(address indexed payer, address indexed receiver, uint256 indexed inputTokenIndex, uint256 totalTransfer, uint256 amountInUint, uint256 feeUintActual);
|
|
||||||
event BurnSwap(address indexed payer, address indexed receiver, uint256 indexed inputTokenIndex, uint256 amountOutUint);
|
|
||||||
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
|
||||||
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
|
||||||
|
|
||||||
|
|
||||||
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
|
||||||
/// @dev swapMint executes as an exact-in planned swap followed by proportional scaling of qInternal.
|
|
||||||
/// The function emits SwapMint (gross, net, fee) and also emits Mint for LP issuance.
|
|
||||||
/// @param payer who transfers the input token
|
|
||||||
/// @param receiver who receives the minted LP tokens
|
|
||||||
/// @param inputTokenIndex index of the input token
|
|
||||||
/// @param maxAmountIn maximum uint token input (inclusive of fee)
|
|
||||||
/// @param deadline optional deadline
|
|
||||||
/// @param swapFeePpm fee in parts-per-million for this pool
|
|
||||||
/// @return lpMinted actual LP minted (uint)
|
|
||||||
function swapMint(
|
|
||||||
address payer,
|
|
||||||
address receiver,
|
|
||||||
uint256 inputTokenIndex,
|
|
||||||
uint256 maxAmountIn,
|
|
||||||
uint256 deadline,
|
|
||||||
uint256 swapFeePpm
|
|
||||||
) external returns (uint256 lpMinted, uint256 feeUintActual) {
|
|
||||||
uint256 n = tokens.length;
|
|
||||||
require(inputTokenIndex < n, "swapMint: idx");
|
|
||||||
require(maxAmountIn > 0, "swapMint: input zero");
|
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "swapMint: deadline");
|
|
||||||
require(lmsr.nAssets > 0, "swapMint: uninit pool");
|
|
||||||
|
|
||||||
// compute fee on gross maxAmountIn to get an initial net estimate (we'll recompute based on actual used)
|
|
||||||
(, uint256 netUintGuess) = _computeFee(maxAmountIn, swapFeePpm);
|
|
||||||
|
|
||||||
// Convert the net guess to internal (floor)
|
|
||||||
int128 netInternalGuess = _uintToInternalFloor(netUintGuess, bases[inputTokenIndex]);
|
|
||||||
require(netInternalGuess > int128(0), "swapMint: input too small after fee");
|
|
||||||
|
|
||||||
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
|
|
||||||
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) = lmsr.swapAmountsForMint(inputTokenIndex, netInternalGuess);
|
|
||||||
|
|
||||||
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
|
|
||||||
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, bases[inputTokenIndex]);
|
|
||||||
require(amountInUint > 0, "swapMint: input zero after internal conversion");
|
|
||||||
|
|
||||||
// Compute fee on the actual used input and total transfer amount (ceiling)
|
|
||||||
feeUintActual = _ceilFee(amountInUint, swapFeePpm);
|
|
||||||
uint256 totalTransfer = amountInUint + feeUintActual;
|
|
||||||
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max");
|
|
||||||
|
|
||||||
// Record pre-balance and transfer tokens from payer, require exact receipt (revert on fee-on-transfer)
|
|
||||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
||||||
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransfer);
|
|
||||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
||||||
require(balIAfter == prevBalI + totalTransfer, "swapMint: non-standard tokenIn");
|
|
||||||
|
|
||||||
// Update cached uint balances for token inputTokenIndex (implementation writes onchain value; wrapper will set effective)
|
|
||||||
cachedUintBalances[inputTokenIndex] = balIAfter;
|
|
||||||
|
|
||||||
// Compute old and new scaled size metrics to determine LP minted
|
|
||||||
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
|
||||||
require(oldTotal > int128(0), "swapMint: zero total");
|
|
||||||
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
|
||||||
|
|
||||||
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
|
|
||||||
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
|
||||||
|
|
||||||
uint256 actualLpToMint;
|
|
||||||
// Use natural ERC20 function since base contract inherits from ERC20
|
|
||||||
uint256 currentSupply = _totalSupply;
|
|
||||||
if (currentSupply == 0) {
|
|
||||||
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
|
||||||
actualLpToMint = newScaled;
|
|
||||||
} else {
|
|
||||||
require(oldScaled > 0, "swapMint: oldScaled zero");
|
|
||||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
|
||||||
if (delta > 0) {
|
|
||||||
// floor truncation rounds in favor of pool
|
|
||||||
actualLpToMint = (currentSupply * delta) / oldScaled;
|
|
||||||
} else {
|
|
||||||
actualLpToMint = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
require(actualLpToMint > 0, "swapMint: zero LP minted");
|
|
||||||
|
|
||||||
// Update LMSR internal state: scale qInternal proportionally by newTotal/oldTotal
|
|
||||||
int128[] memory newQInternal = new int128[](n);
|
|
||||||
for (uint256 idx = 0; idx < n; idx++) {
|
|
||||||
// newQInternal[idx] = qInternal[idx] * (newTotal / oldTotal)
|
|
||||||
newQInternal[idx] = lmsr.qInternal[idx].mul(newTotal).div(oldTotal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update cached internal and kappa via updateForProportionalChange
|
|
||||||
lmsr.updateForProportionalChange(newQInternal);
|
|
||||||
|
|
||||||
// Use natural ERC20 function since base contract inherits from ERC20
|
|
||||||
_mint(receiver, actualLpToMint);
|
|
||||||
|
|
||||||
// Emit SwapMint event with gross transfer, net input and fee (planned exact-in)
|
|
||||||
emit SwapMint(payer, receiver, inputTokenIndex, totalTransfer, amountInUint, feeUintActual);
|
|
||||||
|
|
||||||
// Emit standard Mint event which records deposit amounts and LP minted
|
|
||||||
emit Mint(payer, receiver, new uint256[](n), actualLpToMint);
|
|
||||||
// Note: depositAmounts array omitted (empty) since swapMint uses single-token input
|
|
||||||
|
|
||||||
lpMinted = actualLpToMint;
|
|
||||||
return (lpMinted, feeUintActual);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Calculate the amounts for a swap mint operation
|
|
||||||
/// @dev This is a pure view function that computes swap mint amounts from provided state
|
|
||||||
/// @param inputTokenIndex index of the input token
|
|
||||||
/// @param maxAmountIn maximum amount of token to deposit (inclusive of fee)
|
|
||||||
/// @param swapFeePpm fee in parts-per-million
|
|
||||||
/// @param lmsrState current LMSR state
|
|
||||||
/// @param bases_ scaling bases for each token
|
|
||||||
/// @param totalSupply_ current total LP token supply
|
|
||||||
/// @return amountInUsed actual input amount used (excluding fee)
|
|
||||||
/// @return fee fee amount charged
|
|
||||||
/// @return lpMinted LP tokens that would be minted
|
|
||||||
function swapMintAmounts(
|
|
||||||
uint256 inputTokenIndex,
|
|
||||||
uint256 maxAmountIn,
|
|
||||||
uint256 swapFeePpm,
|
|
||||||
LMSRStabilized.State memory lmsrState,
|
|
||||||
uint256[] memory bases_,
|
|
||||||
uint256 totalSupply_
|
|
||||||
) public pure returns (uint256 amountInUsed, uint256 fee, uint256 lpMinted) {
|
|
||||||
require(inputTokenIndex < bases_.length, "swapMintAmounts: idx");
|
|
||||||
require(maxAmountIn > 0, "swapMintAmounts: input zero");
|
|
||||||
require(lmsrState.nAssets > 0, "swapMintAmounts: uninit pool");
|
|
||||||
|
|
||||||
// Compute fee on gross maxAmountIn to get an initial net estimate
|
|
||||||
uint256 feeGuess = 0;
|
|
||||||
uint256 netUintGuess = maxAmountIn;
|
|
||||||
if (swapFeePpm > 0) {
|
|
||||||
feeGuess = (maxAmountIn * swapFeePpm + 999999) / 1000000; // ceil fee
|
|
||||||
netUintGuess = maxAmountIn - feeGuess;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the net guess to internal (floor)
|
|
||||||
int128 netInternalGuess = _uintToInternalFloorPure(netUintGuess, bases_[inputTokenIndex]);
|
|
||||||
require(netInternalGuess > int128(0), "swapMintAmounts: input too small after fee");
|
|
||||||
|
|
||||||
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
|
|
||||||
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) =
|
|
||||||
LMSRStabilized.swapAmountsForMint(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
|
||||||
inputTokenIndex, netInternalGuess);
|
|
||||||
|
|
||||||
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
|
|
||||||
amountInUsed = _internalToUintCeilPure(amountInInternalUsed, bases_[inputTokenIndex]);
|
|
||||||
require(amountInUsed > 0, "swapMintAmounts: input zero after internal conversion");
|
|
||||||
|
|
||||||
// Compute fee on the actual used input (ceiling)
|
|
||||||
fee = 0;
|
|
||||||
if (swapFeePpm > 0) {
|
|
||||||
fee = (amountInUsed * swapFeePpm + 999999) / 1000000; // ceil fee
|
|
||||||
}
|
|
||||||
uint256 totalTransfer = amountInUsed + fee;
|
|
||||||
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMintAmounts: transfer exceeds max");
|
|
||||||
|
|
||||||
// Compute old and new scaled size metrics to determine LP minted
|
|
||||||
int128 oldTotal = _computeSizeMetricPure(lmsrState.qInternal);
|
|
||||||
require(oldTotal > int128(0), "swapMintAmounts: zero total");
|
|
||||||
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
|
||||||
|
|
||||||
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
|
|
||||||
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
|
||||||
|
|
||||||
if (totalSupply_ == 0) {
|
|
||||||
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
|
||||||
lpMinted = newScaled;
|
|
||||||
} else {
|
|
||||||
require(oldScaled > 0, "swapMintAmounts: oldScaled zero");
|
|
||||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
|
||||||
if (delta > 0) {
|
|
||||||
// floor truncation rounds in favor of pool
|
|
||||||
lpMinted = (totalSupply_ * delta) / oldScaled;
|
|
||||||
} else {
|
|
||||||
lpMinted = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
require(lpMinted > 0, "swapMintAmounts: zero LP minted");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Calculate the amounts for a burn swap operation
|
|
||||||
/// @dev This is a pure view function that computes burn swap amounts from provided state
|
|
||||||
/// @param lpAmount amount of LP tokens to burn
|
|
||||||
/// @param inputTokenIndex index of target asset to receive
|
|
||||||
/// @param swapFeePpm fee in parts-per-million
|
|
||||||
/// @param lmsrState current LMSR state
|
|
||||||
/// @param bases_ scaling bases for each token
|
|
||||||
/// @param totalSupply_ current total LP token supply
|
|
||||||
/// @return amountOut amount of target asset that would be received
|
|
||||||
function burnSwapAmounts(
|
|
||||||
uint256 lpAmount,
|
|
||||||
uint256 inputTokenIndex,
|
|
||||||
uint256 swapFeePpm,
|
|
||||||
LMSRStabilized.State memory lmsrState,
|
|
||||||
uint256[] memory bases_,
|
|
||||||
uint256 totalSupply_
|
|
||||||
) public pure returns (uint256 amountOut) {
|
|
||||||
require(inputTokenIndex < bases_.length, "burnSwapAmounts: idx");
|
|
||||||
require(lpAmount > 0, "burnSwapAmounts: zero lp");
|
|
||||||
require(totalSupply_ > 0, "burnSwapAmounts: empty supply");
|
|
||||||
|
|
||||||
// alpha = lpAmount / supply as Q64.64
|
|
||||||
int128 alpha = ABDKMath64x64.divu(lpAmount, totalSupply_) // fraction of total supply to burn
|
|
||||||
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
|
|
||||||
|
|
||||||
// Use LMSR view to compute single-asset payout and burned size-metric
|
|
||||||
(int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
|
||||||
inputTokenIndex, alpha);
|
|
||||||
|
|
||||||
// Convert payoutInternal -> uint (floor) to favor pool
|
|
||||||
amountOut = _internalToUintFloorPure(payoutInternal, bases_[inputTokenIndex]);
|
|
||||||
require(amountOut > 0, "burnSwapAmounts: output zero");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
|
|
||||||
/// @dev The function burns LP tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state.
|
|
||||||
/// @param payer who burns LP tokens
|
|
||||||
/// @param receiver who receives the single asset
|
|
||||||
/// @param lpAmount amount of LP tokens to burn
|
|
||||||
/// @param inputTokenIndex index of target asset to receive
|
|
||||||
/// @param deadline optional deadline
|
|
||||||
/// @param swapFeePpm fee in parts-per-million for this pool (may be used for future fee logic)
|
|
||||||
/// @return amountOutUint uint amount of asset i sent to receiver
|
|
||||||
function burnSwap(
|
|
||||||
address payer,
|
|
||||||
address receiver,
|
|
||||||
uint256 lpAmount,
|
|
||||||
uint256 inputTokenIndex,
|
|
||||||
uint256 deadline,
|
|
||||||
uint256 swapFeePpm
|
|
||||||
) external returns (uint256 amountOutUint, uint256 feeTokenUint) {
|
|
||||||
uint256 n = tokens.length;
|
|
||||||
require(inputTokenIndex < n, "burnSwap: idx");
|
|
||||||
require(lpAmount > 0, "burnSwap: zero lp");
|
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline");
|
|
||||||
|
|
||||||
uint256 supply = _totalSupply;
|
|
||||||
require(supply > 0, "burnSwap: empty supply");
|
|
||||||
require(_balances[payer] >= lpAmount, "burnSwap: insufficient LP");
|
|
||||||
|
|
||||||
// alpha = lpAmount / supply as Q64.64 (adjusted for fee)
|
|
||||||
int128 alpha = ABDKMath64x64.divu(lpAmount, supply) // fraction of total supply to burn
|
|
||||||
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
|
|
||||||
|
|
||||||
// Use LMSR view to compute single-asset payout and burned size-metric
|
|
||||||
(int128 payoutInternal, ) = lmsr.swapAmountsForBurn(inputTokenIndex, alpha);
|
|
||||||
|
|
||||||
// Convert payoutInternal -> uint (floor) to favor pool
|
|
||||||
amountOutUint = _internalToUintFloor(payoutInternal, bases[inputTokenIndex]);
|
|
||||||
require(amountOutUint > 0, "burnSwap: output zero");
|
|
||||||
|
|
||||||
// Compute gross payout (no swap fee) so we can determine token-side fee = gross - net
|
|
||||||
int128 alphaGross = ABDKMath64x64.divu(lpAmount, supply); // gross fraction (no swap fee)
|
|
||||||
(int128 payoutGrossInternal, ) = lmsr.swapAmountsForBurn(inputTokenIndex, alphaGross);
|
|
||||||
uint256 payoutGrossUint = _internalToUintFloor(payoutGrossInternal, bases[inputTokenIndex]);
|
|
||||||
feeTokenUint = (payoutGrossUint > amountOutUint) ? (payoutGrossUint - amountOutUint) : 0;
|
|
||||||
|
|
||||||
// Transfer the payout to receiver
|
|
||||||
tokens[inputTokenIndex].safeTransfer(receiver, amountOutUint);
|
|
||||||
|
|
||||||
// Burn LP tokens from payer (authorization via allowance)
|
|
||||||
if (msg.sender != payer) {
|
|
||||||
uint256 allowed = _allowances[payer][msg.sender];
|
|
||||||
require(allowed >= lpAmount, "burnSwap: allowance insufficient");
|
|
||||||
_approve(payer, msg.sender, allowed - lpAmount);
|
|
||||||
}
|
|
||||||
_burn(payer, lpAmount);
|
|
||||||
|
|
||||||
// Update cached balances by reading on-chain balances for all tokens
|
|
||||||
int128[] memory newQInternal = new int128[](n);
|
|
||||||
for (uint256 idx = 0; idx < n; idx++) {
|
|
||||||
uint256 bal = IERC20(tokens[idx]).balanceOf(address(this));
|
|
||||||
// implementation writes raw onchain values; wrapper will set effective cached values
|
|
||||||
cachedUintBalances[idx] = bal;
|
|
||||||
newQInternal[idx] = _uintToInternalFloor(bal, bases[idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit BurnSwap with public-facing info only (do not expose ΔS or LP burned)
|
|
||||||
emit BurnSwap(payer, receiver, inputTokenIndex, amountOutUint);
|
|
||||||
|
|
||||||
// If entire pool drained, deinit; else update proportionally
|
|
||||||
bool allZero = true;
|
|
||||||
for (uint256 idx = 0; idx < n; idx++) {
|
|
||||||
if (newQInternal[idx] != int128(0)) { allZero = false; break; }
|
|
||||||
}
|
|
||||||
if (allZero) {
|
|
||||||
lmsr.deinit();
|
|
||||||
} else {
|
|
||||||
lmsr.updateForProportionalChange(newQInternal);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit Burn(payer, receiver, new uint256[](n), lpAmount);
|
|
||||||
return (amountOutUint, feeTokenUint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Pure version of _uintToInternalFloor for use in view functions
|
|
||||||
function _uintToInternalFloorPure(uint256 amount, uint256 base) internal pure returns (int128) {
|
|
||||||
// amount / base as Q64.64, floored
|
|
||||||
return ABDKMath64x64.divu(amount, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Pure version of _internalToUintCeil for use in view functions
|
|
||||||
function _internalToUintCeilPure(int128 amount, uint256 base) internal pure returns (uint256) {
|
|
||||||
// Convert Q64.64 to uint with ceiling: ceil(amount * base)
|
|
||||||
// Use mulu which floors, then add remainder check for ceiling
|
|
||||||
uint256 floored = ABDKMath64x64.mulu(amount, base);
|
|
||||||
// Check if there's a fractional part by computing amount * base - floored
|
|
||||||
int128 baseQ64 = ABDKMath64x64.fromUInt(base);
|
|
||||||
int128 flooredQ64 = ABDKMath64x64.fromUInt(floored);
|
|
||||||
int128 product = amount.mul(baseQ64);
|
|
||||||
if (product > flooredQ64) {
|
|
||||||
return floored + 1; // Ceiling
|
|
||||||
}
|
|
||||||
return floored;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Pure version of _internalToUintFloor for use in view functions
|
|
||||||
function _internalToUintFloorPure(int128 amount, uint256 base) internal pure returns (uint256) {
|
|
||||||
// Convert Q64.64 to uint with floor: floor(amount * base)
|
|
||||||
return ABDKMath64x64.mulu(amount, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @notice Pure version of _computeSizeMetric for use in view functions
|
|
||||||
function _computeSizeMetricPure(int128[] memory qInternal) internal pure returns (int128) {
|
|
||||||
int128 sum = int128(0);
|
|
||||||
for (uint256 i = 0; i < qInternal.length; i++) {
|
|
||||||
sum = sum.add(qInternal[i]);
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user