PartyPoolMintImpl
This commit is contained in:
@@ -9,7 +9,7 @@ remappings = [
|
|||||||
optimizer=true
|
optimizer=true
|
||||||
optimizer_runs=999999999
|
optimizer_runs=999999999
|
||||||
viaIR=true
|
viaIR=true
|
||||||
gas_reports = ['PartyPool', 'PartyPlanner', 'PartyPoolSwapMintImpl', 'PartyPoolViewImpl']
|
gas_reports = ['PartyPool', 'PartyPlanner', 'PartyPoolSwapMintImpl', 'PartyPoolMintImpl',]
|
||||||
fs_permissions = [{ access = "write", path = "chain.json"}]
|
fs_permissions = [{ access = "write", path = "chain.json"}]
|
||||||
|
|
||||||
[lint]
|
[lint]
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ pragma solidity ^0.8.30;
|
|||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import {PartyPool} from "./PartyPool.sol";
|
import {PartyPool} from "./PartyPool.sol";
|
||||||
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
||||||
|
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||||
import {PartyPlanner} from "./PartyPlanner.sol";
|
import {PartyPlanner} from "./PartyPlanner.sol";
|
||||||
|
|
||||||
library Deploy {
|
library Deploy {
|
||||||
|
|
||||||
function newPartyPlanner() internal returns (PartyPlanner) {
|
function newPartyPlanner() internal returns (PartyPlanner) {
|
||||||
return new PartyPlanner(
|
return new PartyPlanner(
|
||||||
new PartyPoolSwapMintImpl()
|
new PartyPoolSwapMintImpl(),
|
||||||
|
new PartyPoolMintImpl()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +27,8 @@ library Deploy {
|
|||||||
bool _stable
|
bool _stable
|
||||||
) internal returns (PartyPool) {
|
) internal returns (PartyPool) {
|
||||||
return new PartyPool(name_, symbol_, tokens_, bases_, _kappa, _swapFeePpm, _flashFeePpm, _stable,
|
return new PartyPool(name_, symbol_, tokens_, bases_, _kappa, _swapFeePpm, _flashFeePpm, _stable,
|
||||||
new PartyPoolSwapMintImpl()
|
new PartyPoolSwapMintImpl(),
|
||||||
|
address(new PartyPoolMintImpl())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import "./LMSRStabilized.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";
|
||||||
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
||||||
|
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||||
|
|
||||||
/// @title PartyPlanner
|
/// @title PartyPlanner
|
||||||
/// @notice Factory contract for creating and tracking PartyPool instances
|
/// @notice Factory contract for creating and tracking PartyPool instances
|
||||||
@@ -17,6 +18,9 @@ contract PartyPlanner is IPartyPlanner {
|
|||||||
/// @notice Address of the SwapMint implementation contract used by all pools created by this factory
|
/// @notice Address of the SwapMint implementation contract used by all pools created by this factory
|
||||||
address public immutable swapMintImpl;
|
address public immutable swapMintImpl;
|
||||||
|
|
||||||
|
/// @notice Address of the Mint implementation contract used by all pools created by this factory
|
||||||
|
address public immutable mintImpl;
|
||||||
|
|
||||||
// On-chain pool indexing
|
// On-chain pool indexing
|
||||||
PartyPool[] private _allPools;
|
PartyPool[] private _allPools;
|
||||||
IERC20[] private _allTokens;
|
IERC20[] private _allTokens;
|
||||||
@@ -25,9 +29,12 @@ contract PartyPlanner is IPartyPlanner {
|
|||||||
mapping(IERC20 => PartyPool[]) private _poolsByToken;
|
mapping(IERC20 => PartyPool[]) private _poolsByToken;
|
||||||
|
|
||||||
/// @param _swapMintImpl address of the SwapMint implementation contract to be used by all pools
|
/// @param _swapMintImpl address of the SwapMint implementation contract to be used by all pools
|
||||||
constructor(PartyPoolSwapMintImpl _swapMintImpl) {
|
/// @param _mintImpl address of the Mint implementation contract to be used by all pools
|
||||||
require(address(_swapMintImpl) != address(0), "Planner: impl address cannot be zero");
|
constructor(PartyPoolSwapMintImpl _swapMintImpl, PartyPoolMintImpl _mintImpl) {
|
||||||
|
require(address(_swapMintImpl) != address(0), "Planner: swapMintImpl address cannot be zero");
|
||||||
swapMintImpl = address(_swapMintImpl);
|
swapMintImpl = address(_swapMintImpl);
|
||||||
|
require(address(_mintImpl) != address(0), "Planner: mintImpl address cannot be zero");
|
||||||
|
mintImpl = address(_mintImpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main newPool variant: accepts kappa directly (preferred).
|
/// Main newPool variant: accepts kappa directly (preferred).
|
||||||
@@ -67,7 +74,8 @@ contract PartyPlanner is IPartyPlanner {
|
|||||||
_swapFeePpm,
|
_swapFeePpm,
|
||||||
_flashFeePpm,
|
_flashFeePpm,
|
||||||
_stable,
|
_stable,
|
||||||
PartyPoolSwapMintImpl(swapMintImpl)
|
PartyPoolSwapMintImpl(swapMintImpl),
|
||||||
|
mintImpl
|
||||||
);
|
);
|
||||||
|
|
||||||
_allPools.push(pool);
|
_allPools.push(pool);
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
/// @notice Address of the SwapMint implementation contract for delegatecall
|
/// @notice Address of the SwapMint implementation contract for delegatecall
|
||||||
address public immutable swapMintImpl;
|
address public immutable swapMintImpl;
|
||||||
|
|
||||||
|
/// @notice Address of the Mint implementation contract for delegatecall
|
||||||
|
address public immutable mintImpl;
|
||||||
|
|
||||||
/// @inheritdoc IPartyPool
|
/// @inheritdoc IPartyPool
|
||||||
function getToken(uint256 i) external view returns (IERC20) { return tokens[i]; }
|
function getToken(uint256 i) external view returns (IERC20) { return tokens[i]; }
|
||||||
|
|
||||||
@@ -69,6 +72,7 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
/// @param _flashFeePpm fee in parts-per-million, taken for flash loans
|
/// @param _flashFeePpm fee in parts-per-million, taken for flash loans
|
||||||
/// @param _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated.
|
/// @param _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated.
|
||||||
/// @param _swapMintImpl address of the SwapMint implementation contract
|
/// @param _swapMintImpl address of the SwapMint implementation contract
|
||||||
|
/// @param _mintImpl address of the Mint implementation contract
|
||||||
constructor(
|
constructor(
|
||||||
string memory name_,
|
string memory name_,
|
||||||
string memory symbol_,
|
string memory symbol_,
|
||||||
@@ -78,7 +82,8 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
uint256 _swapFeePpm,
|
uint256 _swapFeePpm,
|
||||||
uint256 _flashFeePpm,
|
uint256 _flashFeePpm,
|
||||||
bool _stable,
|
bool _stable,
|
||||||
PartyPoolSwapMintImpl _swapMintImpl
|
PartyPoolSwapMintImpl _swapMintImpl,
|
||||||
|
address _mintImpl
|
||||||
) PartyPoolBase(name_, symbol_) {
|
) PartyPoolBase(name_, symbol_) {
|
||||||
require(tokens_.length > 1, "Pool: need >1 asset");
|
require(tokens_.length > 1, "Pool: need >1 asset");
|
||||||
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
|
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
|
||||||
@@ -90,8 +95,10 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
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;
|
_stablePair = _stable && tokens_.length == 2;
|
||||||
require(address(_swapMintImpl) != address(0), "Pool: impl address zero");
|
require(address(_swapMintImpl) != address(0), "Pool: swapMintImpl address zero");
|
||||||
swapMintImpl = address(_swapMintImpl);
|
swapMintImpl = address(_swapMintImpl);
|
||||||
|
require(_mintImpl != address(0), "Pool: mintImpl address zero");
|
||||||
|
mintImpl = _mintImpl;
|
||||||
|
|
||||||
uint256 n = tokens_.length;
|
uint256 n = tokens_.length;
|
||||||
|
|
||||||
@@ -115,6 +122,10 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
|
|
||||||
/// @inheritdoc IPartyPool
|
/// @inheritdoc IPartyPool
|
||||||
function mintDepositAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
|
function mintDepositAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
|
||||||
|
return _mintDepositAmounts(lpTokenAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mintDepositAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory depositAmounts) {
|
||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
depositAmounts = new uint256[](n);
|
depositAmounts = new uint256[](n);
|
||||||
|
|
||||||
@@ -180,77 +191,23 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Proportional mint for existing pool.
|
/// @notice Proportional mint for existing pool.
|
||||||
/// @dev Payer must approve the required token amounts before calling.
|
/// @dev This function forwards the call to the mint implementation via delegatecall
|
||||||
/// Can only be called when pool is already initialized (totalSupply() > 0 and lmsr.nAssets > 0).
|
|
||||||
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
|
|
||||||
/// @param payer address that provides the input tokens
|
/// @param payer address that provides the input tokens
|
||||||
/// @param receiver address that receives the LP tokens
|
/// @param receiver address that receives the LP tokens
|
||||||
/// @param lpTokenAmount desired amount of LP tokens to mint
|
/// @param lpTokenAmount desired amount of LP tokens to mint
|
||||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||||
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external nonReentrant
|
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external nonReentrant
|
||||||
returns (uint256 lpMinted) {
|
returns (uint256 lpMinted) {
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
bytes memory data = abi.encodeWithSignature(
|
||||||
uint256 n = tokens.length;
|
"mint(address,address,uint256,uint256)",
|
||||||
|
payer,
|
||||||
|
receiver,
|
||||||
|
lpTokenAmount,
|
||||||
|
deadline
|
||||||
|
);
|
||||||
|
|
||||||
// Check if this is NOT initial deposit - revert if it is
|
bytes memory result = Address.functionDelegateCall(mintImpl, data);
|
||||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
return abi.decode(result, (uint256));
|
||||||
require(!isInitialDeposit, "mint: use initialMint for pool initialization");
|
|
||||||
require(lpTokenAmount > 0, "mint: zero LP amount");
|
|
||||||
|
|
||||||
// Capture old pool size metric (scaled) by computing from current balances
|
|
||||||
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
|
||||||
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
|
||||||
|
|
||||||
// Calculate required deposit amounts for the desired LP tokens
|
|
||||||
uint256[] memory depositAmounts = mintDepositAmounts(lpTokenAmount);
|
|
||||||
|
|
||||||
// Transfer in all token amounts
|
|
||||||
for (uint i = 0; i < n; ) {
|
|
||||||
if (depositAmounts[i] > 0) {
|
|
||||||
tokens[i].safeTransferFrom(payer, address(this), depositAmounts[i]);
|
|
||||||
}
|
|
||||||
unchecked { i++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update cached balances for all assets
|
|
||||||
int128[] memory newQInternal = new int128[](n);
|
|
||||||
for (uint i = 0; i < n; ) {
|
|
||||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
|
||||||
cachedUintBalances[i] = bal;
|
|
||||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
|
||||||
unchecked { i++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update for proportional change
|
|
||||||
lmsr.updateForProportionalChange(newQInternal);
|
|
||||||
|
|
||||||
// Compute actual LP tokens to mint based on change in size metric (scaled)
|
|
||||||
// floor truncation rounds in favor of the pool
|
|
||||||
int128 newTotal = _computeSizeMetric(newQInternal);
|
|
||||||
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
|
||||||
uint256 actualLpToMint;
|
|
||||||
|
|
||||||
require(oldScaled > 0, "mint: oldScaled zero");
|
|
||||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
|
||||||
// Proportional issuance: totalSupply * delta / oldScaled
|
|
||||||
if (delta > 0) {
|
|
||||||
// floor truncation rounds in favor of the pool
|
|
||||||
actualLpToMint = (totalSupply() * delta) / oldScaled;
|
|
||||||
} else {
|
|
||||||
actualLpToMint = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the calculated LP amount is not too different from requested
|
|
||||||
require(actualLpToMint > 0, "mint: zero LP minted");
|
|
||||||
|
|
||||||
// Allow actual amount to be at most 0.00001% less than requested
|
|
||||||
// This accounts for rounding in deposit calculations
|
|
||||||
uint256 minAcceptable = lpTokenAmount * 99_999 / 100_000;
|
|
||||||
require(actualLpToMint >= minAcceptable, "mint: insufficient LP minted");
|
|
||||||
|
|
||||||
_mint(receiver, actualLpToMint);
|
|
||||||
emit Mint(payer, receiver, depositAmounts, actualLpToMint);
|
|
||||||
return actualLpToMint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @inheritdoc IPartyPool
|
/// @inheritdoc IPartyPool
|
||||||
@@ -280,74 +237,21 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
|
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
|
||||||
/// @dev Payer must own or approve the LP tokens being burned. The function updates LMSR state
|
/// @dev This function forwards the call to the burn implementation via delegatecall
|
||||||
/// proportionally to reflect the reduced pool size after the withdrawal.
|
|
||||||
/// @param payer address that provides the LP tokens to burn
|
/// @param payer address that provides the LP tokens to burn
|
||||||
/// @param receiver address that receives the withdrawn tokens
|
/// @param receiver address that receives the withdrawn tokens
|
||||||
/// @param lpAmount amount of LP tokens to burn (proportional withdrawal)
|
/// @param lpAmount amount of LP tokens to burn (proportional withdrawal)
|
||||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||||
function burn(address payer, address receiver, uint256 lpAmount, uint256 deadline) external nonReentrant {
|
function burn(address payer, address receiver, uint256 lpAmount, uint256 deadline) external nonReentrant {
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "burn: deadline exceeded");
|
bytes memory data = abi.encodeWithSignature(
|
||||||
uint256 n = tokens.length;
|
"burn(address,address,uint256,uint256)",
|
||||||
require(lpAmount > 0, "burn: zero lp");
|
payer,
|
||||||
|
receiver,
|
||||||
|
lpAmount,
|
||||||
|
deadline
|
||||||
|
);
|
||||||
|
|
||||||
uint256 supply = totalSupply();
|
Address.functionDelegateCall(mintImpl, data);
|
||||||
require(supply > 0, "burn: empty supply");
|
|
||||||
require(lmsr.nAssets > 0, "burn: uninit pool");
|
|
||||||
require(balanceOf(payer) >= lpAmount, "burn: insufficient LP");
|
|
||||||
|
|
||||||
// Refresh cached balances to reflect current on-chain balances before computing withdrawal amounts
|
|
||||||
for (uint i = 0; i < n; ) {
|
|
||||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
|
||||||
cachedUintBalances[i] = bal;
|
|
||||||
unchecked { i++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
|
||||||
uint256[] memory withdrawAmounts = _burnReceiveAmounts(lpAmount);
|
|
||||||
|
|
||||||
// Transfer underlying tokens out to receiver according to computed proportions
|
|
||||||
for (uint i = 0; i < n; ) {
|
|
||||||
if (withdrawAmounts[i] > 0) {
|
|
||||||
tokens[i].safeTransfer(receiver, withdrawAmounts[i]);
|
|
||||||
}
|
|
||||||
unchecked { i++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update cached balances and internal q for all assets
|
|
||||||
int128[] memory newQInternal = new int128[](n);
|
|
||||||
for (uint i = 0; i < n; ) {
|
|
||||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
|
||||||
cachedUintBalances[i] = bal;
|
|
||||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
|
||||||
unchecked { i++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply proportional update or deinitialize if drained
|
|
||||||
bool allZero = true;
|
|
||||||
for (uint i = 0; i < n; ) {
|
|
||||||
if (newQInternal[i] != int128(0)) {
|
|
||||||
allZero = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
unchecked { i++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allZero) {
|
|
||||||
lmsr.deinit();
|
|
||||||
} else {
|
|
||||||
lmsr.updateForProportionalChange(newQInternal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Burn exactly the requested LP amount from payer (authorization via allowance)
|
|
||||||
if (msg.sender != payer) {
|
|
||||||
uint256 allowed = allowance(payer, msg.sender);
|
|
||||||
require(allowed >= lpAmount, "burn: allowance insufficient");
|
|
||||||
_approve(payer, msg.sender, allowed - lpAmount);
|
|
||||||
}
|
|
||||||
_burn(payer, lpAmount);
|
|
||||||
|
|
||||||
emit Burn(payer, receiver, withdrawAmounts, lpAmount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------
|
/* ----------------------
|
||||||
|
|||||||
215
src/PartyPoolMintImpl.sol
Normal file
215
src/PartyPoolMintImpl.sol
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
// 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 "./PartyPoolBase.sol";
|
||||||
|
import "./LMSRStabilized.sol";
|
||||||
|
|
||||||
|
/// @title PartyPoolMintImpl - Implementation contract for mint and burn functions
|
||||||
|
/// @notice This contract contains the mint and burn implementation that will be called via delegatecall
|
||||||
|
/// @dev This contract inherits from PartyPoolBase to access storage and internal functions
|
||||||
|
contract PartyPoolMintImpl is PartyPoolBase {
|
||||||
|
using ABDKMath64x64 for int128;
|
||||||
|
using LMSRStabilized for LMSRStabilized.State;
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
// Events that mirror the main contract events
|
||||||
|
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
||||||
|
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
||||||
|
|
||||||
|
constructor() PartyPoolBase('','') {}
|
||||||
|
|
||||||
|
/// @notice Proportional mint for existing pool.
|
||||||
|
/// @dev Payer must approve the required token amounts before calling.
|
||||||
|
/// Can only be called when pool is already initialized (totalSupply() > 0 and lmsr.nAssets > 0).
|
||||||
|
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
|
||||||
|
/// @param payer address that provides the input tokens
|
||||||
|
/// @param receiver address that receives the LP tokens
|
||||||
|
/// @param lpTokenAmount desired amount of LP tokens to mint
|
||||||
|
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||||
|
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external returns (uint256 lpMinted) {
|
||||||
|
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
|
||||||
|
// Check if this is NOT initial deposit - revert if it is
|
||||||
|
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||||
|
require(!isInitialDeposit, "mint: use initialMint for pool initialization");
|
||||||
|
require(lpTokenAmount > 0, "mint: zero LP amount");
|
||||||
|
|
||||||
|
// Capture old pool size metric (scaled) by computing from current balances
|
||||||
|
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
||||||
|
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||||
|
|
||||||
|
// Calculate required deposit amounts for the desired LP tokens
|
||||||
|
uint256[] memory depositAmounts = _mintDepositAmounts(lpTokenAmount);
|
||||||
|
|
||||||
|
// Transfer in all token amounts
|
||||||
|
for (uint i = 0; i < n; ) {
|
||||||
|
if (depositAmounts[i] > 0) {
|
||||||
|
tokens[i].safeTransferFrom(payer, address(this), depositAmounts[i]);
|
||||||
|
}
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cached balances for all assets
|
||||||
|
int128[] memory newQInternal = new int128[](n);
|
||||||
|
for (uint i = 0; i < n; ) {
|
||||||
|
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||||
|
cachedUintBalances[i] = bal;
|
||||||
|
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update for proportional change
|
||||||
|
lmsr.updateForProportionalChange(newQInternal);
|
||||||
|
|
||||||
|
// Compute actual LP tokens to mint based on change in size metric (scaled)
|
||||||
|
// floor truncation rounds in favor of the pool
|
||||||
|
int128 newTotal = _computeSizeMetric(newQInternal);
|
||||||
|
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||||
|
uint256 actualLpToMint;
|
||||||
|
|
||||||
|
require(oldScaled > 0, "mint: oldScaled zero");
|
||||||
|
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||||
|
// Proportional issuance: totalSupply * delta / oldScaled
|
||||||
|
if (delta > 0) {
|
||||||
|
// floor truncation rounds in favor of the pool
|
||||||
|
actualLpToMint = (totalSupply() * delta) / oldScaled;
|
||||||
|
} else {
|
||||||
|
actualLpToMint = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the calculated LP amount is not too different from requested
|
||||||
|
require(actualLpToMint > 0, "mint: zero LP minted");
|
||||||
|
|
||||||
|
// Allow actual amount to be at most 0.00001% less than requested
|
||||||
|
// This accounts for rounding in deposit calculations
|
||||||
|
uint256 minAcceptable = lpTokenAmount * 99_999 / 100_000;
|
||||||
|
require(actualLpToMint >= minAcceptable, "mint: insufficient LP minted");
|
||||||
|
|
||||||
|
_mint(receiver, actualLpToMint);
|
||||||
|
emit Mint(payer, receiver, depositAmounts, actualLpToMint);
|
||||||
|
return actualLpToMint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
|
||||||
|
/// @dev Payer must own or approve the LP tokens being burned. The function updates LMSR state
|
||||||
|
/// proportionally to reflect the reduced pool size after the withdrawal.
|
||||||
|
/// @param payer address that provides the LP tokens to burn
|
||||||
|
/// @param receiver address that receives the withdrawn tokens
|
||||||
|
/// @param lpAmount amount of LP tokens to burn (proportional withdrawal)
|
||||||
|
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||||
|
function burn(address payer, address receiver, uint256 lpAmount, uint256 deadline) external {
|
||||||
|
require(deadline == 0 || block.timestamp <= deadline, "burn: deadline exceeded");
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
require(lpAmount > 0, "burn: zero lp");
|
||||||
|
|
||||||
|
uint256 supply = totalSupply();
|
||||||
|
require(supply > 0, "burn: empty supply");
|
||||||
|
require(lmsr.nAssets > 0, "burn: uninit pool");
|
||||||
|
require(balanceOf(payer) >= lpAmount, "burn: insufficient LP");
|
||||||
|
|
||||||
|
// Refresh cached balances to reflect current on-chain balances before computing withdrawal amounts
|
||||||
|
for (uint i = 0; i < n; ) {
|
||||||
|
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||||
|
cachedUintBalances[i] = bal;
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
||||||
|
uint256[] memory withdrawAmounts = _burnReceiveAmounts(lpAmount);
|
||||||
|
|
||||||
|
// Transfer underlying tokens out to receiver according to computed proportions
|
||||||
|
for (uint i = 0; i < n; ) {
|
||||||
|
if (withdrawAmounts[i] > 0) {
|
||||||
|
tokens[i].safeTransfer(receiver, withdrawAmounts[i]);
|
||||||
|
}
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cached balances and internal q for all assets
|
||||||
|
int128[] memory newQInternal = new int128[](n);
|
||||||
|
for (uint i = 0; i < n; ) {
|
||||||
|
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||||
|
cachedUintBalances[i] = bal;
|
||||||
|
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply proportional update or deinitialize if drained
|
||||||
|
bool allZero = true;
|
||||||
|
for (uint i = 0; i < n; ) {
|
||||||
|
if (newQInternal[i] != int128(0)) {
|
||||||
|
allZero = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allZero) {
|
||||||
|
lmsr.deinit();
|
||||||
|
} else {
|
||||||
|
lmsr.updateForProportionalChange(newQInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Burn exactly the requested LP amount from payer (authorization via allowance)
|
||||||
|
if (msg.sender != payer) {
|
||||||
|
uint256 allowed = allowance(payer, msg.sender);
|
||||||
|
require(allowed >= lpAmount, "burn: allowance insufficient");
|
||||||
|
_approve(payer, msg.sender, allowed - lpAmount);
|
||||||
|
}
|
||||||
|
_burn(payer, lpAmount);
|
||||||
|
|
||||||
|
emit Burn(payer, receiver, withdrawAmounts, lpAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Internal helper to calculate required deposit amounts for minting LP tokens
|
||||||
|
function _mintDepositAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory depositAmounts) {
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
depositAmounts = new uint256[](n);
|
||||||
|
|
||||||
|
// If this is the first mint or pool is empty, return zeros
|
||||||
|
// For first mint, tokens should already be transferred to the pool
|
||||||
|
if (totalSupply() == 0 || lmsr.nAssets == 0) {
|
||||||
|
return depositAmounts; // Return zeros, initial deposit handled differently
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate deposit based on current proportions
|
||||||
|
uint256 totalLpSupply = totalSupply();
|
||||||
|
|
||||||
|
// lpTokenAmount / totalLpSupply = depositAmount / currentBalance
|
||||||
|
// Therefore: depositAmount = (lpTokenAmount * currentBalance) / totalLpSupply
|
||||||
|
// We round up to protect the pool
|
||||||
|
for (uint i = 0; i < n; i++) {
|
||||||
|
uint256 currentBalance = cachedUintBalances[i];
|
||||||
|
// Calculate with rounding up: (a * b + c - 1) / c
|
||||||
|
depositAmounts[i] = (lpTokenAmount * currentBalance + totalLpSupply - 1) / totalLpSupply;
|
||||||
|
}
|
||||||
|
|
||||||
|
return depositAmounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Internal helper to calculate withdrawal amounts for burning LP tokens
|
||||||
|
function _burnReceiveAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory withdrawAmounts) {
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
withdrawAmounts = new uint256[](n);
|
||||||
|
|
||||||
|
// If supply is zero or pool uninitialized, return zeros
|
||||||
|
if (totalSupply() == 0 || lmsr.nAssets == 0) {
|
||||||
|
return withdrawAmounts; // Return zeros, nothing to withdraw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate withdrawal amounts based on current proportions
|
||||||
|
uint256 totalLpSupply = totalSupply();
|
||||||
|
|
||||||
|
// withdrawAmount = floor(lpTokenAmount * currentBalance / totalLpSupply)
|
||||||
|
for (uint i = 0; i < n; i++) {
|
||||||
|
uint256 currentBalance = cachedUintBalances[i];
|
||||||
|
withdrawAmounts[i] = (lpTokenAmount * currentBalance) / totalLpSupply;
|
||||||
|
}
|
||||||
|
|
||||||
|
return withdrawAmounts;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user