swap amounts
This commit is contained in:
5
bin/mock
5
bin/mock
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
CODE_SIZE_LIMIT=65000
|
CODE_SIZE_LIMIT=65000
|
||||||
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
# Dev account #0
|
||||||
|
#PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||||
|
# Dev account #4
|
||||||
|
PRIVATE_KEY=0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a
|
||||||
|
|
||||||
# Function to cleanup processes
|
# Function to cleanup processes
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
|||||||
@@ -286,6 +286,8 @@ def plot_static(res):
|
|||||||
plt.ylabel("y per x")
|
plt.ylabel("y per x")
|
||||||
plt.legend()
|
plt.legend()
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
|
plt.xlim(right=1)
|
||||||
|
plt.ylim( bottom=.999,top=1)
|
||||||
|
|
||||||
plt.figure(figsize=(10, 6))
|
plt.figure(figsize=(10, 6))
|
||||||
plt.plot(q, res["welfare_gap"] / res["p0"] * 1e4)
|
plt.plot(q, res["welfare_gap"] / res["p0"] * 1e4)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ contract DeployMock is Script {
|
|||||||
address constant devAccount0 = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
|
address constant devAccount0 = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
|
||||||
// private key 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
|
// private key 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
|
||||||
address constant devAccount7 = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955;
|
address constant devAccount7 = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955;
|
||||||
address constant deployer = devAccount0;
|
|
||||||
|
|
||||||
function run() public {
|
function run() public {
|
||||||
vm.startBroadcast();
|
vm.startBroadcast();
|
||||||
@@ -41,7 +40,7 @@ contract DeployMock is Script {
|
|||||||
|
|
||||||
// initial mint
|
// initial mint
|
||||||
mintAll(address(pool), 10_000);
|
mintAll(address(pool), 10_000);
|
||||||
pool.mint(deployer, deployer, 0, 0);
|
pool.mint(devAccount7, devAccount7, 0, 0);
|
||||||
|
|
||||||
// give tokens to dev7
|
// give tokens to dev7
|
||||||
mintAll(devAccount7, 1_000_000);
|
mintAll(devAccount7, 1_000_000);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ interface IPartyPool is IERC20Metadata {
|
|||||||
/// @notice Calculate the proportional deposit amounts required for a given LP token amount
|
/// @notice Calculate the proportional deposit amounts required for a given LP token amount
|
||||||
/// @param lpTokenAmount The amount of LP tokens desired
|
/// @param lpTokenAmount The amount of LP tokens desired
|
||||||
/// @return depositAmounts Array of token amounts to deposit (rounded up)
|
/// @return depositAmounts Array of token amounts to deposit (rounded up)
|
||||||
function computeMintAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory depositAmounts);
|
function mintDepositAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory depositAmounts);
|
||||||
|
|
||||||
/// @notice Proportional mint (or initial supply if first call).
|
/// @notice Proportional mint (or initial supply if first call).
|
||||||
/// For initial supply: assumes tokens have already been transferred to the pool
|
/// For initial supply: assumes tokens have already been transferred to the pool
|
||||||
@@ -74,7 +74,7 @@ interface IPartyPool is IERC20Metadata {
|
|||||||
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount
|
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount
|
||||||
/// @param lpTokenAmount The amount of LP tokens to burn
|
/// @param lpTokenAmount The amount of LP tokens to burn
|
||||||
/// @return withdrawAmounts Array of token amounts to withdraw (rounded down)
|
/// @return withdrawAmounts Array of token amounts to withdraw (rounded down)
|
||||||
function computeBurnAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts);
|
function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts);
|
||||||
|
|
||||||
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
|
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
|
||||||
/// Payer must own the LP tokens; withdraw amounts are computed from current proportions.
|
/// Payer must own the LP tokens; withdraw amounts are computed from current proportions.
|
||||||
@@ -86,6 +86,15 @@ interface IPartyPool is IERC20Metadata {
|
|||||||
|
|
||||||
|
|
||||||
// Swaps
|
// Swaps
|
||||||
|
|
||||||
|
/// @notice External view to quote exact-in swap amounts (gross input incl. fee and output), matching swap() computations
|
||||||
|
function swapAmounts(
|
||||||
|
uint256 i,
|
||||||
|
uint256 j,
|
||||||
|
uint256 maxAmountIn,
|
||||||
|
int128 limitPrice
|
||||||
|
) external view returns (uint256 amountIn, uint256 amountOut);
|
||||||
|
|
||||||
function swap(
|
function swap(
|
||||||
address payer,
|
address payer,
|
||||||
address receiver,
|
address receiver,
|
||||||
@@ -96,6 +105,13 @@ interface IPartyPool is IERC20Metadata {
|
|||||||
uint256 deadline
|
uint256 deadline
|
||||||
) external returns (uint256 amountIn, uint256 amountOut);
|
) external returns (uint256 amountIn, uint256 amountOut);
|
||||||
|
|
||||||
|
/// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations
|
||||||
|
function swapToLimitAmounts(
|
||||||
|
uint256 i,
|
||||||
|
uint256 j,
|
||||||
|
int128 limitPrice
|
||||||
|
) external view returns (uint256 amountIn, uint256 amountOut);
|
||||||
|
|
||||||
function swapToLimit(
|
function swapToLimit(
|
||||||
address payer,
|
address payer,
|
||||||
address receiver,
|
address receiver,
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
/// @notice Calculate the proportional deposit amounts required for a given LP token amount
|
/// @notice Calculate the proportional deposit amounts required for a given LP token amount
|
||||||
/// @param lpTokenAmount The amount of LP tokens desired
|
/// @param lpTokenAmount The amount of LP tokens desired
|
||||||
/// @return depositAmounts Array of token amounts to deposit (rounded up)
|
/// @return depositAmounts Array of token amounts to deposit (rounded up)
|
||||||
function computeMintAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
|
function mintDepositAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
|
||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
depositAmounts = new uint256[](n);
|
depositAmounts = new uint256[](n);
|
||||||
|
|
||||||
@@ -136,11 +136,11 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount
|
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount
|
||||||
/// @param lpTokenAmount The amount of LP tokens to burn
|
/// @param lpTokenAmount The amount of LP tokens to burn
|
||||||
/// @return withdrawAmounts Array of token amounts to withdraw (rounded down)
|
/// @return withdrawAmounts Array of token amounts to withdraw (rounded down)
|
||||||
function computeBurnAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
|
function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
|
||||||
return _computeBurnAmounts(lpTokenAmount);
|
return _burnReceiveAmounts(lpTokenAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _computeBurnAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory withdrawAmounts) {
|
function _burnReceiveAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory withdrawAmounts) {
|
||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
withdrawAmounts = new uint256[](n);
|
withdrawAmounts = new uint256[](n);
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
|
|
||||||
if (!isInitialDeposit) {
|
if (!isInitialDeposit) {
|
||||||
// Calculate required deposit amounts for the desired LP tokens
|
// Calculate required deposit amounts for the desired LP tokens
|
||||||
depositAmounts = computeMintAmounts(lpTokenAmount);
|
depositAmounts = mintDepositAmounts(lpTokenAmount);
|
||||||
|
|
||||||
// Transfer in all token amounts
|
// Transfer in all token amounts
|
||||||
for (uint i = 0; i < n; ) {
|
for (uint i = 0; i < n; ) {
|
||||||
@@ -284,7 +284,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
||||||
uint256[] memory withdrawAmounts = _computeBurnAmounts(lpAmount);
|
uint256[] memory withdrawAmounts = _burnReceiveAmounts(lpAmount);
|
||||||
|
|
||||||
// Transfer underlying tokens out to receiver according to computed proportions
|
// Transfer underlying tokens out to receiver according to computed proportions
|
||||||
for (uint i = 0; i < n; ) {
|
for (uint i = 0; i < n; ) {
|
||||||
@@ -334,6 +334,119 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
Swaps
|
Swaps
|
||||||
---------------------- */
|
---------------------- */
|
||||||
|
|
||||||
|
/// @notice Internal quote for exact-input swap that mirrors swap() rounding and fee application
|
||||||
|
/// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
|
||||||
|
/// amountInInternalUsed and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint)
|
||||||
|
function _quoteSwapExactIn(
|
||||||
|
uint256 i,
|
||||||
|
uint256 j,
|
||||||
|
uint256 maxAmountIn,
|
||||||
|
int128 limitPrice
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
uint256 grossIn,
|
||||||
|
uint256 amountOutUint,
|
||||||
|
int128 amountInInternalUsed,
|
||||||
|
int128 amountOutInternal,
|
||||||
|
uint256 amountInUintNoFee
|
||||||
|
)
|
||||||
|
{
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
require(i < n && j < n, "swap: idx");
|
||||||
|
require(maxAmountIn > 0, "swap: input zero");
|
||||||
|
require(lmsr.nAssets > 0, "swap: empty pool");
|
||||||
|
|
||||||
|
// Estimate max net input (fee on gross rounded up, then subtract)
|
||||||
|
(, uint256 netUintForSwap) = _computeFee(maxAmountIn);
|
||||||
|
|
||||||
|
// Convert to internal (floor)
|
||||||
|
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[i]);
|
||||||
|
require(deltaInternalI > int128(0), "swap: input too small after fee");
|
||||||
|
|
||||||
|
// Compute internal amounts using LMSR (exact-input with price limit)
|
||||||
|
(amountInInternalUsed, amountOutInternal) = lmsr.swapAmountsForExactInput(i, j, deltaInternalI, limitPrice);
|
||||||
|
|
||||||
|
// Convert actual used input internal -> uint (ceil)
|
||||||
|
amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, bases[i]);
|
||||||
|
require(amountInUintNoFee > 0, "swap: input zero");
|
||||||
|
|
||||||
|
// Compute gross transfer including fee on the used input (ceil)
|
||||||
|
grossIn = amountInUintNoFee;
|
||||||
|
if (swapFeePpm > 0) {
|
||||||
|
grossIn += _ceilFee(amountInUintNoFee, swapFeePpm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure within user max
|
||||||
|
require(grossIn <= maxAmountIn, "swap: transfer exceeds max");
|
||||||
|
|
||||||
|
// Compute output (floor)
|
||||||
|
amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
|
||||||
|
require(amountOutUint > 0, "swap: output zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Internal quote for swap-to-limit that mirrors swapToLimit() rounding and fee application
|
||||||
|
/// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
|
||||||
|
/// amountInInternal and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint)
|
||||||
|
function _quoteSwapToLimit(
|
||||||
|
uint256 i,
|
||||||
|
uint256 j,
|
||||||
|
int128 limitPrice
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
uint256 grossIn,
|
||||||
|
uint256 amountOutUint,
|
||||||
|
int128 amountInInternal,
|
||||||
|
int128 amountOutInternal,
|
||||||
|
uint256 amountInUintNoFee
|
||||||
|
)
|
||||||
|
{
|
||||||
|
uint256 n = tokens.length;
|
||||||
|
require(i < n && j < n, "swapToLimit: idx");
|
||||||
|
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
||||||
|
require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized");
|
||||||
|
|
||||||
|
// Compute internal maxima at the price limit
|
||||||
|
(amountInInternal, amountOutInternal) = lmsr.swapAmountsForPriceLimit(i, j, limitPrice);
|
||||||
|
|
||||||
|
// Convert input to uint (ceil) and output to uint (floor)
|
||||||
|
amountInUintNoFee = _internalToUintCeil(amountInInternal, bases[i]);
|
||||||
|
require(amountInUintNoFee > 0, "swapToLimit: input zero");
|
||||||
|
|
||||||
|
grossIn = amountInUintNoFee;
|
||||||
|
if (swapFeePpm > 0) {
|
||||||
|
grossIn += _ceilFee(amountInUintNoFee, swapFeePpm);
|
||||||
|
}
|
||||||
|
|
||||||
|
amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
|
||||||
|
require(amountOutUint > 0, "swapToLimit: output zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice External view to quote exact-in swap amounts (gross input incl. fee and output), matching swap() computations
|
||||||
|
function swapAmounts(
|
||||||
|
uint256 i,
|
||||||
|
uint256 j,
|
||||||
|
uint256 maxAmountIn,
|
||||||
|
int128 limitPrice
|
||||||
|
) external view returns (uint256 amountIn, uint256 amountOut) {
|
||||||
|
(uint256 grossIn, uint256 outUint,,,) = _quoteSwapExactIn(i, j, maxAmountIn, limitPrice);
|
||||||
|
return (grossIn, outUint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations
|
||||||
|
function swapToLimitAmounts(
|
||||||
|
uint256 i,
|
||||||
|
uint256 j,
|
||||||
|
int128 limitPrice
|
||||||
|
) external view returns (uint256 amountIn, uint256 amountOut) {
|
||||||
|
(uint256 grossIn, uint256 outUint,,,) = _quoteSwapToLimit(i, j, limitPrice);
|
||||||
|
return (grossIn, outUint);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// @notice Swap input token i -> token j. Payer must approve token i.
|
/// @notice Swap input token i -> token j. Payer must approve token i.
|
||||||
/// @param payer address of the account that pays for the swap
|
/// @param payer address of the account that pays for the swap
|
||||||
/// @param receiver address that will receive the output tokens
|
/// @param receiver address that will receive the output tokens
|
||||||
@@ -361,47 +474,15 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this));
|
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this));
|
||||||
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
|
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
|
||||||
|
|
||||||
// Calculate fee (ceiling) and net amount
|
// Compute amounts using the same path as views
|
||||||
(, uint256 netUintForSwap) = _computeFee(maxAmountIn);
|
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, ) =
|
||||||
|
_quoteSwapExactIn(i, j, maxAmountIn, limitPrice);
|
||||||
// Convert the net amount to internal (floor)
|
|
||||||
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[i]);
|
|
||||||
require(deltaInternalI > int128(0), "swap: input too small after fee");
|
|
||||||
|
|
||||||
// Make sure LMSR state exists
|
|
||||||
require(lmsr.nAssets > 0, "swap: empty pool");
|
|
||||||
|
|
||||||
// Compute swap amounts in internal space using exact-input logic (with limitPrice)
|
|
||||||
(int128 amountInInternalUsed, int128 amountOutInternal) = lmsr.swapAmountsForExactInput(
|
|
||||||
i,
|
|
||||||
j,
|
|
||||||
deltaInternalI,
|
|
||||||
limitPrice
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert actual used input internal -> uint (ceiling to protect the pool)
|
|
||||||
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, bases[i]);
|
|
||||||
|
|
||||||
// Total transfer amount includes fee calculated on the actual used input (ceiling)
|
|
||||||
uint256 totalTransferAmount = amountInUint;
|
|
||||||
if (swapFeePpm > 0) {
|
|
||||||
uint256 feeOnUsed = _ceilFee(amountInUint, swapFeePpm);
|
|
||||||
totalTransferAmount += feeOnUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we do not attempt to transfer more than the caller specified as maximum
|
|
||||||
require(totalTransferAmount > 0, 'swap: input zero');
|
|
||||||
require(totalTransferAmount <= maxAmountIn, "swap: transfer exceeds max");
|
|
||||||
|
|
||||||
// Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer)
|
// Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer)
|
||||||
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount);
|
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount);
|
||||||
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this));
|
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this));
|
||||||
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
|
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
|
||||||
|
|
||||||
// Compute output uint amount (floor)
|
|
||||||
uint256 amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
|
|
||||||
require(amountOutUint > 0, "swap: output zero");
|
|
||||||
|
|
||||||
// Transfer output to receiver and verify exact decrease
|
// Transfer output to receiver and verify exact decrease
|
||||||
_safeTransfer(tokens[j], receiver, amountOutUint);
|
_safeTransfer(tokens[j], receiver, amountOutUint);
|
||||||
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this));
|
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this));
|
||||||
@@ -411,8 +492,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
cachedUintBalances[i] = balIAfter;
|
cachedUintBalances[i] = balIAfter;
|
||||||
cachedUintBalances[j] = balJAfter;
|
cachedUintBalances[j] = balJAfter;
|
||||||
|
|
||||||
// Apply swap to LMSR state with the internal amounts actually used
|
// Apply swap to LMSR state with the internal amounts actually used
|
||||||
// (fee is already accounted for in the reduced input amount)
|
|
||||||
lmsr.applySwap(i, j, amountInInternalUsed, amountOutInternal);
|
lmsr.applySwap(i, j, amountInInternalUsed, amountOutInternal);
|
||||||
|
|
||||||
emit Swap(payer, receiver, tokens[i], tokens[j], totalTransferAmount, amountOutUint);
|
emit Swap(payer, receiver, tokens[i], tokens[j], totalTransferAmount, amountOutUint);
|
||||||
@@ -437,37 +517,19 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
|
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
|
||||||
|
|
||||||
// Ensure LMSR state exists
|
|
||||||
require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized");
|
|
||||||
|
|
||||||
// Read previous balances for affected assets
|
// Read previous balances for affected assets
|
||||||
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this));
|
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this));
|
||||||
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
|
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
|
||||||
|
|
||||||
// Compute maxima in internal space using library
|
// Compute amounts using the same path as views
|
||||||
(int128 amountInInternalMax, int128 amountOutInternal) = lmsr.swapAmountsForPriceLimit(i, j, limitPrice);
|
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint) =
|
||||||
|
_quoteSwapToLimit(i, j, limitPrice);
|
||||||
// Calculate how much input will be needed with fee included (ceiling to protect the pool)
|
|
||||||
uint256 amountInUsedUint = _internalToUintCeil(amountInInternalMax, bases[i]);
|
|
||||||
require(amountInUsedUint > 0, "swapToLimit: input zero");
|
|
||||||
|
|
||||||
// Total transfer amount is the input amount including what will be taken as fee (ceiling)
|
|
||||||
uint256 totalTransferAmount = amountInUsedUint;
|
|
||||||
|
|
||||||
if (swapFeePpm > 0) {
|
|
||||||
uint256 feeOnUsed = _ceilFee(amountInUsedUint, swapFeePpm);
|
|
||||||
totalTransferAmount += feeOnUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
|
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
|
||||||
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount);
|
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount);
|
||||||
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this));
|
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this));
|
||||||
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
|
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
|
||||||
|
|
||||||
// Compute output amount (floor)
|
|
||||||
uint256 amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
|
|
||||||
require(amountOutUint > 0, "swapToLimit: output zero");
|
|
||||||
|
|
||||||
// Transfer output to receiver and verify exact decrease
|
// Transfer output to receiver and verify exact decrease
|
||||||
_safeTransfer(tokens[j], receiver, amountOutUint);
|
_safeTransfer(tokens[j], receiver, amountOutUint);
|
||||||
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this));
|
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this));
|
||||||
@@ -478,9 +540,9 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
cachedUintBalances[j] = balJAfter;
|
cachedUintBalances[j] = balJAfter;
|
||||||
|
|
||||||
// Apply swap to LMSR state with the internal amounts
|
// Apply swap to LMSR state with the internal amounts
|
||||||
// (fee is already part of the reduced effective input)
|
|
||||||
lmsr.applySwap(i, j, amountInInternalMax, amountOutInternal);
|
lmsr.applySwap(i, j, amountInInternalMax, amountOutInternal);
|
||||||
|
|
||||||
|
// Maintain original event semantics (logs input without fee)
|
||||||
emit Swap(payer, receiver, tokens[i], tokens[j], amountInUsedUint, amountOutUint);
|
emit Swap(payer, receiver, tokens[i], tokens[j], amountInUsedUint, amountOutUint);
|
||||||
|
|
||||||
return (amountInUsedUint, amountOutUint);
|
return (amountInUsedUint, amountOutUint);
|
||||||
@@ -685,7 +747,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function computeFlashRepaymentAmounts(uint256[] memory loanAmounts) external view
|
function flashRepaymentAmounts(uint256[] memory loanAmounts) external view
|
||||||
returns (uint256[] memory repaymentAmounts) {
|
returns (uint256[] memory repaymentAmounts) {
|
||||||
repaymentAmounts = new uint256[](tokens.length);
|
repaymentAmounts = new uint256[](tokens.length);
|
||||||
for (uint256 i = 0; i < tokens.length; i++) {
|
for (uint256 i = 0; i < tokens.length; i++) {
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ contract PartyPoolTest is Test {
|
|||||||
token2.approve(address(pool), type(uint256).max);
|
token2.approve(address(pool), type(uint256).max);
|
||||||
|
|
||||||
// Inspect the deposit amounts that the pool will require (these are rounded up)
|
// Inspect the deposit amounts that the pool will require (these are rounded up)
|
||||||
uint256[] memory deposits = pool.computeMintAmounts(1);
|
uint256[] memory deposits = pool.mintDepositAmounts(1);
|
||||||
|
|
||||||
// Basic sanity: deposits array length must match token count and not all zero necessarily
|
// Basic sanity: deposits array length must match token count and not all zero necessarily
|
||||||
assertEq(deposits.length, 3);
|
assertEq(deposits.length, 3);
|
||||||
@@ -358,7 +358,7 @@ contract PartyPoolTest is Test {
|
|||||||
uint256 totalLpBefore = pool.totalSupply();
|
uint256 totalLpBefore = pool.totalSupply();
|
||||||
|
|
||||||
// Compute required deposits and perform mint for 1 wei
|
// Compute required deposits and perform mint for 1 wei
|
||||||
uint256[] memory deposits = pool.computeMintAmounts(1);
|
uint256[] memory deposits = pool.mintDepositAmounts(1);
|
||||||
|
|
||||||
// Sum deposits as deposited_value
|
// Sum deposits as deposited_value
|
||||||
uint256 depositedValue = 0;
|
uint256 depositedValue = 0;
|
||||||
@@ -392,14 +392,14 @@ contract PartyPoolTest is Test {
|
|||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice computeMintAmounts should round up deposit amounts to protect the pool.
|
/// @notice mintDepositAmounts should round up deposit amounts to protect the pool.
|
||||||
function testComputeMintAmountsRoundingUp() public view {
|
function testMintDepositAmountsRoundingUp() public view {
|
||||||
uint256 totalLp = pool.totalSupply();
|
uint256 totalLp = pool.totalSupply();
|
||||||
assertTrue(totalLp > 0, "precondition: total supply > 0");
|
assertTrue(totalLp > 0, "precondition: total supply > 0");
|
||||||
|
|
||||||
// Request half of LP supply
|
// Request half of LP supply
|
||||||
uint256 want = totalLp / 2;
|
uint256 want = totalLp / 2;
|
||||||
uint256[] memory deposits = pool.computeMintAmounts(want);
|
uint256[] memory deposits = pool.mintDepositAmounts(want);
|
||||||
|
|
||||||
// We expect each deposit to be roughly half the pool balance, but due to rounding up it should satisfy:
|
// We expect each deposit to be roughly half the pool balance, but due to rounding up it should satisfy:
|
||||||
// deposits[i] * 2 >= cached balance (i.e., rounding up)
|
// deposits[i] * 2 >= cached balance (i.e., rounding up)
|
||||||
@@ -416,7 +416,7 @@ contract PartyPoolTest is Test {
|
|||||||
assertTrue(totalLp > 0, "precondition: LP > 0");
|
assertTrue(totalLp > 0, "precondition: LP > 0");
|
||||||
|
|
||||||
// Compute amounts required to redeem entire supply (should be current balances)
|
// Compute amounts required to redeem entire supply (should be current balances)
|
||||||
uint256[] memory withdrawAmounts = pool.computeBurnAmounts(totalLp);
|
uint256[] memory withdrawAmounts = pool.burnReceiveAmounts(totalLp);
|
||||||
|
|
||||||
// Sanity: withdrawAmounts should equal pool balances (or very close due to rounding)
|
// Sanity: withdrawAmounts should equal pool balances (or very close due to rounding)
|
||||||
for (uint i = 0; i < withdrawAmounts.length; i++) {
|
for (uint i = 0; i < withdrawAmounts.length; i++) {
|
||||||
@@ -632,8 +632,8 @@ contract PartyPoolTest is Test {
|
|||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Verify computeMintAmounts matches the actual token transfers performed by mint()
|
/// @notice Verify mintDepositAmounts matches the actual token transfers performed by mint()
|
||||||
function testComputeMintAmountsMatchesMint_3TokenPool() public {
|
function testMintDepositAmountsMatchesMint_3TokenPool() public {
|
||||||
// Use a range of LP requests (tiny to large fraction)
|
// Use a range of LP requests (tiny to large fraction)
|
||||||
uint256 totalLp = pool.totalSupply();
|
uint256 totalLp = pool.totalSupply();
|
||||||
uint256[] memory requests = new uint256[](4);
|
uint256[] memory requests = new uint256[](4);
|
||||||
@@ -646,7 +646,7 @@ contract PartyPoolTest is Test {
|
|||||||
if (req == 0) req = 1;
|
if (req == 0) req = 1;
|
||||||
|
|
||||||
// Compute expected deposit amounts via view
|
// Compute expected deposit amounts via view
|
||||||
uint256[] memory expected = pool.computeMintAmounts(req);
|
uint256[] memory expected = pool.mintDepositAmounts(req);
|
||||||
|
|
||||||
// Ensure alice has tokens and approve pool
|
// Ensure alice has tokens and approve pool
|
||||||
vm.startPrank(alice);
|
vm.startPrank(alice);
|
||||||
@@ -660,7 +660,7 @@ contract PartyPoolTest is Test {
|
|||||||
uint256 a2Before = token2.balanceOf(alice);
|
uint256 a2Before = token2.balanceOf(alice);
|
||||||
|
|
||||||
// Perform mint (may revert for zero-request; ensure req>0 above)
|
// Perform mint (may revert for zero-request; ensure req>0 above)
|
||||||
// Guard: if computeMintAmounts returned all zeros, skip (nothing to transfer)
|
// Guard: if mintDepositAmounts returned all zeros, skip (nothing to transfer)
|
||||||
bool allZero = (expected[0] == 0 && expected[1] == 0 && expected[2] == 0);
|
bool allZero = (expected[0] == 0 && expected[1] == 0 && expected[2] == 0);
|
||||||
if (!allZero) {
|
if (!allZero) {
|
||||||
uint256 lpBefore = pool.balanceOf(alice);
|
uint256 lpBefore = pool.balanceOf(alice);
|
||||||
@@ -679,8 +679,8 @@ contract PartyPoolTest is Test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Verify computeMintAmounts matches the actual token transfers performed by mint() for 10-token pool
|
/// @notice Verify mintDepositAmounts matches the actual token transfers performed by mint() for 10-token pool
|
||||||
function testComputeMintAmountsMatchesMint_10TokenPool() public {
|
function testMintDepositAmountsMatchesMint_10TokenPool() public {
|
||||||
uint256 totalLp = pool10.totalSupply();
|
uint256 totalLp = pool10.totalSupply();
|
||||||
uint256[] memory requests = new uint256[](4);
|
uint256[] memory requests = new uint256[](4);
|
||||||
requests[0] = 1;
|
requests[0] = 1;
|
||||||
@@ -691,7 +691,7 @@ contract PartyPoolTest is Test {
|
|||||||
uint256 req = requests[k];
|
uint256 req = requests[k];
|
||||||
if (req == 0) req = 1;
|
if (req == 0) req = 1;
|
||||||
|
|
||||||
uint256[] memory expected = pool10.computeMintAmounts(req);
|
uint256[] memory expected = pool10.mintDepositAmounts(req);
|
||||||
|
|
||||||
// Approve all tokens from alice
|
// Approve all tokens from alice
|
||||||
vm.startPrank(alice);
|
vm.startPrank(alice);
|
||||||
@@ -742,8 +742,8 @@ contract PartyPoolTest is Test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Verify computeBurnAmounts matches actual transfers performed by burn() for 3-token pool
|
/// @notice Verify burnReceiveAmounts matches actual transfers performed by burn() for 3-token pool
|
||||||
function testComputeBurnAmountsMatchesBurn_3TokenPool() public {
|
function testBurnReceiveAmountsMatchesBurn_3TokenPool() public {
|
||||||
// Use address(this) as payer (holds initial LP from setUp)
|
// Use address(this) as payer (holds initial LP from setUp)
|
||||||
uint256 totalLp = pool.totalSupply();
|
uint256 totalLp = pool.totalSupply();
|
||||||
uint256[] memory burns = new uint256[](4);
|
uint256[] memory burns = new uint256[](4);
|
||||||
@@ -769,7 +769,7 @@ contract PartyPoolTest is Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recompute withdraw amounts via view after any top-up
|
// Recompute withdraw amounts via view after any top-up
|
||||||
uint256[] memory expected = pool.computeBurnAmounts(req);
|
uint256[] memory expected = pool.burnReceiveAmounts(req);
|
||||||
|
|
||||||
// If expected withdraws are all zero (rounding edge), skip this iteration
|
// If expected withdraws are all zero (rounding edge), skip this iteration
|
||||||
if (expected[0] == 0 && expected[1] == 0 && expected[2] == 0) {
|
if (expected[0] == 0 && expected[1] == 0 && expected[2] == 0) {
|
||||||
@@ -795,8 +795,8 @@ contract PartyPoolTest is Test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Verify computeBurnAmounts matches actual transfers performed by burn() for 10-token pool
|
/// @notice Verify burnReceiveAmounts matches actual transfers performed by burn() for 10-token pool
|
||||||
function testComputeBurnAmountsMatchesBurn_10TokenPool() public {
|
function testBurnReceiveAmountsMatchesBurn_10TokenPool() public {
|
||||||
uint256 totalLp = pool10.totalSupply();
|
uint256 totalLp = pool10.totalSupply();
|
||||||
uint256[] memory burns = new uint256[](4);
|
uint256[] memory burns = new uint256[](4);
|
||||||
burns[0] = 1;
|
burns[0] = 1;
|
||||||
@@ -826,7 +826,7 @@ contract PartyPoolTest is Test {
|
|||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256[] memory expected = pool10.computeBurnAmounts(req);
|
uint256[] memory expected = pool10.burnReceiveAmounts(req);
|
||||||
|
|
||||||
// If expected withdraws are all zero (rounding edge), skip this iteration
|
// If expected withdraws are all zero (rounding edge), skip this iteration
|
||||||
bool allZero = true;
|
bool allZero = true;
|
||||||
@@ -1311,8 +1311,8 @@ contract PartyPoolTest is Test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Test computeFlashRepaymentAmounts matches flash implementation
|
/// @notice Test flashRepaymentAmounts matches flash implementation
|
||||||
function testComputeFlashRepaymentAmounts() public view {
|
function testFlashRepaymentAmounts() public view {
|
||||||
// Create different loan amount scenarios
|
// Create different loan amount scenarios
|
||||||
uint256[][] memory testCases = new uint256[][](3);
|
uint256[][] memory testCases = new uint256[][](3);
|
||||||
|
|
||||||
@@ -1336,7 +1336,7 @@ contract PartyPoolTest is Test {
|
|||||||
|
|
||||||
for (uint256 i = 0; i < testCases.length; i++) {
|
for (uint256 i = 0; i < testCases.length; i++) {
|
||||||
uint256[] memory loanAmounts = testCases[i];
|
uint256[] memory loanAmounts = testCases[i];
|
||||||
uint256[] memory repaymentAmounts = pool.computeFlashRepaymentAmounts(loanAmounts);
|
uint256[] memory repaymentAmounts = pool.flashRepaymentAmounts(loanAmounts);
|
||||||
|
|
||||||
// Verify each repayment amount is correctly calculated
|
// Verify each repayment amount is correctly calculated
|
||||||
for (uint256 j = 0; j < loanAmounts.length; j++) {
|
for (uint256 j = 0; j < loanAmounts.length; j++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user