Compare commits
4 Commits
precompute
...
fixed-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96dc134769 | ||
|
|
2972152e58 | ||
| 452b28d165 | |||
|
|
33aadd333e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,7 +5,7 @@
|
|||||||
/.env
|
/.env
|
||||||
/.env-*
|
/.env-*
|
||||||
/.idea/
|
/.idea/
|
||||||
|
package-lock.json
|
||||||
/broadcast
|
/broadcast
|
||||||
|
|
||||||
*secret*
|
*secret*
|
||||||
|
|||||||
@@ -1,459 +0,0 @@
|
|||||||
const { ethers } = require('ethers');
|
|
||||||
const { Token } = require('@uniswap/sdk-core');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
// Token definitions
|
|
||||||
const ChainId = {
|
|
||||||
MAINNET: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
const USDC_TOKEN = new Token(
|
|
||||||
ChainId.MAINNET,
|
|
||||||
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
||||||
6,
|
|
||||||
'USDC',
|
|
||||||
'USDC'
|
|
||||||
);
|
|
||||||
|
|
||||||
const USDT_TOKEN = new Token(
|
|
||||||
ChainId.MAINNET,
|
|
||||||
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
||||||
6,
|
|
||||||
'USDT',
|
|
||||||
'USDT'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
const QUOTER_ADDRESS = '0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203';
|
|
||||||
const POSITION_MANAGER_ADDRESS = '0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e';
|
|
||||||
|
|
||||||
// Pool ID to fetch pool key from
|
|
||||||
const POOL_ID = '0x8aa4e11cbdf30eedc92100f4c8a31ff748e201d44712cc8c90d189edaa8e4e47';
|
|
||||||
|
|
||||||
// Providers
|
|
||||||
const anvilProvider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545');
|
|
||||||
|
|
||||||
// Quoter contract ABI
|
|
||||||
const QUOTER_ABI = [
|
|
||||||
{
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
components: [
|
|
||||||
{ name: 'currency0', type: 'address' },
|
|
||||||
{ name: 'currency1', type: 'address' },
|
|
||||||
{ name: 'fee', type: 'uint24' },
|
|
||||||
{ name: 'tickSpacing', type: 'int24' },
|
|
||||||
{ name: 'hooks', type: 'address' }
|
|
||||||
],
|
|
||||||
name: 'poolKey',
|
|
||||||
type: 'tuple'
|
|
||||||
},
|
|
||||||
{ name: 'zeroForOne', type: 'bool' },
|
|
||||||
{ name: 'exactAmount', type: 'uint128' },
|
|
||||||
{ name: 'hookData', type: 'bytes' }
|
|
||||||
],
|
|
||||||
name: 'params',
|
|
||||||
type: 'tuple'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'quoteExactInputSingle',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
components: [
|
|
||||||
{ name: 'amountOut', type: 'uint256' }
|
|
||||||
],
|
|
||||||
name: 'result',
|
|
||||||
type: 'tuple'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// StateView ABI for getSlot0 and getLiquidity
|
|
||||||
const STATE_VIEW_ABI = [
|
|
||||||
{
|
|
||||||
inputs: [
|
|
||||||
// { name: 'manager', type: 'address' },
|
|
||||||
{ name: 'poolId', type: 'bytes32' }
|
|
||||||
],
|
|
||||||
name: 'getSlot0',
|
|
||||||
outputs: [
|
|
||||||
{ name: 'sqrtPriceX96', type: 'uint160' },
|
|
||||||
{ name: 'tick', type: 'int24' },
|
|
||||||
{ name: 'protocolFee', type: 'uint24' },
|
|
||||||
{ name: 'lpFee', type: 'uint24' }
|
|
||||||
],
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// StateView contract address for reading pool state
|
|
||||||
const STATE_VIEW_ADDRESS = '0x7ffe42c4a5deea5b0fec41c94c136cf115597227';
|
|
||||||
|
|
||||||
// Position Manager ABI
|
|
||||||
const POSITION_MANAGER_ABI = [
|
|
||||||
{
|
|
||||||
inputs: [{ name: 'poolId', type: 'bytes25' }],
|
|
||||||
name: 'poolKeys',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
components: [
|
|
||||||
{ name: 'currency0', type: 'address' },
|
|
||||||
{ name: 'currency1', type: 'address' },
|
|
||||||
{ name: 'fee', type: 'uint24' },
|
|
||||||
{ name: 'tickSpacing', type: 'int24' },
|
|
||||||
{ name: 'hooks', type: 'address' }
|
|
||||||
],
|
|
||||||
name: 'poolKey',
|
|
||||||
type: 'tuple'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Function to get pool key from Position Manager (uses mainnet)
|
|
||||||
async function getPoolKey() {
|
|
||||||
try {
|
|
||||||
const positionManager = new ethers.Contract(
|
|
||||||
POSITION_MANAGER_ADDRESS,
|
|
||||||
POSITION_MANAGER_ABI,
|
|
||||||
anvilProvider
|
|
||||||
);
|
|
||||||
|
|
||||||
// Extract first 25 bytes (50 hex chars + 0x prefix = 52 chars)
|
|
||||||
const poolIdBytes25 = POOL_ID.slice(0, 52);
|
|
||||||
|
|
||||||
const poolKey = await positionManager.poolKeys(poolIdBytes25);
|
|
||||||
|
|
||||||
return {
|
|
||||||
currency0: poolKey.currency0,
|
|
||||||
currency1: poolKey.currency1,
|
|
||||||
fee: poolKey.fee,
|
|
||||||
tickSpacing: poolKey.tickSpacing,
|
|
||||||
hooks: poolKey.hooks
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching pool key:', error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert sqrtPriceX96 to human-readable price
|
|
||||||
function sqrtPriceX96ToPrice(sqrtPriceX96, decimals0, decimals1) {
|
|
||||||
const Q96 = ethers.BigNumber.from(2).pow(96);
|
|
||||||
const sqrtPrice = ethers.BigNumber.from(sqrtPriceX96);
|
|
||||||
|
|
||||||
// Calculate price = (sqrtPriceX96 / 2^96)^2
|
|
||||||
const numerator = sqrtPrice.mul(sqrtPrice);
|
|
||||||
const denominator = Q96.mul(Q96);
|
|
||||||
|
|
||||||
// Adjust for token decimals
|
|
||||||
const decimalAdjustment = ethers.BigNumber.from(10).pow(decimals0 - decimals1);
|
|
||||||
|
|
||||||
// Convert to float for readability
|
|
||||||
return parseFloat(numerator.toString()) / parseFloat(denominator.toString()) * parseFloat(decimalAdjustment.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get pool price using StateView
|
|
||||||
async function getPoolPriceWithSDK(blockNumber) {
|
|
||||||
try {
|
|
||||||
// Create StateView contract with ethers
|
|
||||||
const stateViewContract = new ethers.Contract(
|
|
||||||
STATE_VIEW_ADDRESS,
|
|
||||||
STATE_VIEW_ABI,
|
|
||||||
anvilProvider
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get slot0 data
|
|
||||||
const slot0 = await stateViewContract.getSlot0(POOL_ID, {
|
|
||||||
blockTag: blockNumber
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert sqrtPriceX96 to human-readable price
|
|
||||||
const price = sqrtPriceX96ToPrice(slot0.sqrtPriceX96, 6, 6); // USDC=6, USDT=6
|
|
||||||
const inversePrice = 1 / price;
|
|
||||||
|
|
||||||
console.log('\n=== Pool State ===');
|
|
||||||
console.log('Block:', blockNumber);
|
|
||||||
console.log('sqrtPriceX96:', slot0.sqrtPriceX96.toString());
|
|
||||||
console.log('Tick:', slot0.tick.toString());
|
|
||||||
console.log('Price (USDT per USDC):', price.toFixed(8));
|
|
||||||
console.log('Price (USDC per USDT):', inversePrice.toFixed(8));
|
|
||||||
console.log('LP Fee:', slot0.lpFee, `(${(slot0.lpFee / 10000).toFixed(2)}%)`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sqrtPriceX96: slot0.sqrtPriceX96.toString(),
|
|
||||||
tick: slot0.tick.toString(),
|
|
||||||
price: price, // Human-readable price
|
|
||||||
inversePrice: inversePrice,
|
|
||||||
protocolFee: slot0.protocolFee,
|
|
||||||
lpFee: slot0.lpFee
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting pool price:', error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Get quote from historical block (10 blocks back) with multiple amount testing
|
|
||||||
async function getQuoteFromHistoricalBlock(poolKey, targetBlock) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Get starting pool price using SDK
|
|
||||||
try {
|
|
||||||
await getPoolPriceWithSDK(targetBlock);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting pool price with SDK:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n=== Testing Multiple Input Amounts ===');
|
|
||||||
console.log('Testing amounts from 1 USDC, increasing by 30% each iteration (limited to 5 iterations)');
|
|
||||||
console.log('Testing both directions: USDC→USDT and USDT→USDC\n');
|
|
||||||
|
|
||||||
// Create a new provider instance that queries at specific block
|
|
||||||
const quoterContract = new ethers.Contract(QUOTER_ADDRESS, QUOTER_ABI, anvilProvider);
|
|
||||||
|
|
||||||
const resultsForward = []; // USDC→USDT
|
|
||||||
const resultsReverse = []; // USDT→USDC
|
|
||||||
let currentAmount = 1; // Start with 1 token
|
|
||||||
const multiplier = 1.3; // 30% increase
|
|
||||||
const maxIterations = 200; // Limit to 5 iterations for testing
|
|
||||||
|
|
||||||
// Test USDC→USDT (forward direction)
|
|
||||||
console.log('\n=== USDC → USDT (Forward Direction) ===');
|
|
||||||
for (let i = 0; i < maxIterations; i++) {
|
|
||||||
// Check if currentAmount exceeds 10 million
|
|
||||||
if (currentAmount > 10000000) {
|
|
||||||
console.log(`\nReached maximum amount limit of 1 million USDC. Stopping iterations.`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n--- Iteration ${i + 1}: ${currentAmount.toFixed(6)} USDC ---`);
|
|
||||||
|
|
||||||
const amountIn = ethers.utils.parseUnits(currentAmount.toFixed(6), USDC_TOKEN.decimals);
|
|
||||||
|
|
||||||
// Make the call at the specific block using overrides
|
|
||||||
const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle({
|
|
||||||
poolKey: poolKey,
|
|
||||||
zeroForOne: true,
|
|
||||||
exactAmount: amountIn.toString(),
|
|
||||||
hookData: '0x00',
|
|
||||||
}, {
|
|
||||||
blockTag: targetBlock // Query at specific historical block
|
|
||||||
});
|
|
||||||
|
|
||||||
const actualAmountOut = ethers.BigNumber.from(quotedAmountOut.amountOut);
|
|
||||||
|
|
||||||
// Calculate ideal amount out (1:1 ratio for stablecoins)
|
|
||||||
const idealAmountOut = amountIn; // Since both USDC and USDT have 6 decimals
|
|
||||||
|
|
||||||
// Calculate the difference from ideal
|
|
||||||
const difference = actualAmountOut.sub(idealAmountOut);
|
|
||||||
const isPositive = difference.gte(0);
|
|
||||||
|
|
||||||
// Calculate slippage in basis points and percentage
|
|
||||||
const slippageBasisPoints = difference.mul(10000).div(idealAmountOut);
|
|
||||||
const slippagePercentage = parseFloat(slippageBasisPoints.toString()) / 100;
|
|
||||||
|
|
||||||
// Calculate effective exchange rate
|
|
||||||
const effectiveRate = parseFloat(ethers.utils.formatUnits(actualAmountOut, USDT_TOKEN.decimals)) / currentAmount;
|
|
||||||
|
|
||||||
// Log results for this amount
|
|
||||||
console.log(`Amount In: ${currentAmount.toFixed(6)} USDC`);
|
|
||||||
console.log(`Amount Out: ${ethers.utils.formatUnits(actualAmountOut, USDT_TOKEN.decimals)} USDT`);
|
|
||||||
console.log(`Effective Rate: ${effectiveRate.toFixed(6)} USDT/USDC`);
|
|
||||||
console.log(`Ideal Rate (1:1): ${currentAmount.toFixed(6)} USDT`);
|
|
||||||
console.log(`Difference: ${isPositive ? '+' : ''}${ethers.utils.formatUnits(difference, USDT_TOKEN.decimals)} USDT`);
|
|
||||||
console.log(`Slippage: ${isPositive ? '+' : ''}${slippagePercentage.toFixed(4)}% (${isPositive ? '+' : ''}${slippageBasisPoints.toString()} basis points)`);
|
|
||||||
|
|
||||||
// Store result
|
|
||||||
resultsForward.push({
|
|
||||||
iteration: i + 1,
|
|
||||||
amountIn: currentAmount,
|
|
||||||
amountInFormatted: currentAmount.toFixed(6) + ' USDC',
|
|
||||||
amountOut: ethers.utils.formatUnits(actualAmountOut, USDT_TOKEN.decimals) + ' USDT',
|
|
||||||
effectiveRate: effectiveRate,
|
|
||||||
slippagePercentage: slippagePercentage,
|
|
||||||
slippageBasisPoints: parseInt(slippageBasisPoints.toString()),
|
|
||||||
isPositive: isPositive
|
|
||||||
});
|
|
||||||
|
|
||||||
// Increase amount by 30% for next iteration
|
|
||||||
currentAmount *= multiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset current amount for reverse direction
|
|
||||||
currentAmount = 1;
|
|
||||||
|
|
||||||
// Test USDT→USDC (reverse direction)
|
|
||||||
console.log('\n\n=== USDT → USDC (Reverse Direction) ===');
|
|
||||||
for (let i = 0; i < maxIterations; i++) {
|
|
||||||
// Check if currentAmount exceeds 1 million
|
|
||||||
if (currentAmount > 10000000) {
|
|
||||||
console.log(`\nReached maximum amount limit of 1 million USDT. Stopping iterations.`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n--- Iteration ${i + 1}: ${currentAmount.toFixed(6)} USDT ---`);
|
|
||||||
|
|
||||||
const amountIn = ethers.utils.parseUnits(currentAmount.toFixed(6), USDT_TOKEN.decimals);
|
|
||||||
|
|
||||||
// Make the call at the specific block using overrides with zeroForOne: false
|
|
||||||
const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle({
|
|
||||||
poolKey: poolKey,
|
|
||||||
zeroForOne: false, // false for USDT→USDC
|
|
||||||
exactAmount: amountIn.toString(),
|
|
||||||
hookData: '0x00',
|
|
||||||
}, {
|
|
||||||
blockTag: targetBlock // Query at specific historical block
|
|
||||||
});
|
|
||||||
|
|
||||||
const actualAmountOut = ethers.BigNumber.from(quotedAmountOut.amountOut);
|
|
||||||
|
|
||||||
// Calculate ideal amount out (1:1 ratio for stablecoins)
|
|
||||||
const idealAmountOut = amountIn; // Since both USDC and USDT have 6 decimals
|
|
||||||
|
|
||||||
// Calculate the difference from ideal
|
|
||||||
const difference = actualAmountOut.sub(idealAmountOut);
|
|
||||||
const isPositive = difference.gte(0);
|
|
||||||
|
|
||||||
// Calculate slippage in basis points and percentage
|
|
||||||
const slippageBasisPoints = difference.mul(10000).div(idealAmountOut);
|
|
||||||
const slippagePercentage = parseFloat(slippageBasisPoints.toString()) / 100;
|
|
||||||
|
|
||||||
// Calculate effective exchange rate
|
|
||||||
const effectiveRate = parseFloat(ethers.utils.formatUnits(actualAmountOut, USDC_TOKEN.decimals)) / currentAmount;
|
|
||||||
|
|
||||||
// Log results for this amount
|
|
||||||
console.log(`Amount In: ${currentAmount.toFixed(6)} USDT`);
|
|
||||||
console.log(`Amount Out: ${ethers.utils.formatUnits(actualAmountOut, USDC_TOKEN.decimals)} USDC`);
|
|
||||||
console.log(`Effective Rate: ${effectiveRate.toFixed(6)} USDC/USDT`);
|
|
||||||
console.log(`Ideal Rate (1:1): ${currentAmount.toFixed(6)} USDC`);
|
|
||||||
console.log(`Difference: ${isPositive ? '+' : ''}${ethers.utils.formatUnits(difference, USDC_TOKEN.decimals)} USDC`);
|
|
||||||
console.log(`Slippage: ${isPositive ? '+' : ''}${slippagePercentage.toFixed(4)}% (${isPositive ? '+' : ''}${slippageBasisPoints.toString()} basis points)`);
|
|
||||||
|
|
||||||
// Store result
|
|
||||||
resultsReverse.push({
|
|
||||||
iteration: i + 1,
|
|
||||||
amountIn: currentAmount,
|
|
||||||
amountInFormatted: currentAmount.toFixed(6) + ' USDT',
|
|
||||||
amountOut: ethers.utils.formatUnits(actualAmountOut, USDC_TOKEN.decimals) + ' USDC',
|
|
||||||
effectiveRate: effectiveRate,
|
|
||||||
slippagePercentage: slippagePercentage,
|
|
||||||
slippageBasisPoints: parseInt(slippageBasisPoints.toString()),
|
|
||||||
isPositive: isPositive
|
|
||||||
});
|
|
||||||
|
|
||||||
// Increase amount by 30% for next iteration
|
|
||||||
currentAmount *= multiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary
|
|
||||||
console.log('\n\n=== Summary of Results ===');
|
|
||||||
|
|
||||||
console.log('\n--- Forward Direction (USDC → USDT) ---');
|
|
||||||
console.log('\nAmount In → Amount Out (Rate) [Slippage]');
|
|
||||||
resultsForward.forEach(r => {
|
|
||||||
console.log(`${r.amountInFormatted} → ${r.amountOut} (${r.effectiveRate.toFixed(6)}) [${r.isPositive ? '+' : ''}${r.slippagePercentage.toFixed(4)}%]`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n--- Reverse Direction (USDT → USDC) ---');
|
|
||||||
console.log('\nAmount In → Amount Out (Rate) [Slippage]');
|
|
||||||
resultsReverse.forEach(r => {
|
|
||||||
console.log(`${r.amountInFormatted} → ${r.amountOut} (${r.effectiveRate.toFixed(6)}) [${r.isPositive ? '+' : ''}${r.slippagePercentage.toFixed(4)}%]`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
blockNumber: targetBlock,
|
|
||||||
forward: resultsForward,
|
|
||||||
reverse: resultsReverse
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting historical quote:', error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to write results to CSV
|
|
||||||
function writeResultsToCSV(blockNumber, priceData, quoteResults) {
|
|
||||||
const filename = `swap_results_block_${blockNumber}.csv`;
|
|
||||||
|
|
||||||
// Create CSV header
|
|
||||||
let csvContent = 'Block Number,USDT per USDC,USDC per USDT,Forward Direction Amount In,Forward Direction Amount Out,Reverse Direction Amount In,Reverse Direction Amount Out\n';
|
|
||||||
|
|
||||||
// Get max number of iterations
|
|
||||||
const maxIterations = Math.max(quoteResults.forward.length, quoteResults.reverse.length);
|
|
||||||
|
|
||||||
// Add data rows
|
|
||||||
for (let i = 0; i < maxIterations; i++) {
|
|
||||||
const forwardResult = quoteResults.forward[i] || { amountIn: '', amountOut: '' };
|
|
||||||
const reverseResult = quoteResults.reverse[i] || { amountIn: '', amountOut: '' };
|
|
||||||
|
|
||||||
// Only include block number and prices in first row
|
|
||||||
if (i === 0) {
|
|
||||||
csvContent += `${blockNumber},${priceData.price.toFixed(8)},${priceData.inversePrice.toFixed(8)},`;
|
|
||||||
} else {
|
|
||||||
csvContent += `,,,`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract just the numbers from the formatted strings
|
|
||||||
const forwardAmountIn = forwardResult.amountIn ? forwardResult.amountIn.toFixed(6) : '';
|
|
||||||
const forwardAmountOut = forwardResult.amountOut ? forwardResult.amountOut.split(' ')[0] : '';
|
|
||||||
const reverseAmountIn = reverseResult.amountIn ? reverseResult.amountIn.toFixed(6) : '';
|
|
||||||
const reverseAmountOut = reverseResult.amountOut ? reverseResult.amountOut.split(' ')[0] : '';
|
|
||||||
|
|
||||||
csvContent += `${forwardAmountIn},${forwardAmountOut},${reverseAmountIn},${reverseAmountOut}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to file
|
|
||||||
fs.writeFileSync(filename, csvContent);
|
|
||||||
console.log(`\n✅ Results written to ${filename}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main function
|
|
||||||
async function main() {
|
|
||||||
console.log('=== Uniswap V4 Quote and Gas Estimation ===\n');
|
|
||||||
// Get current block
|
|
||||||
const currentBlock = await anvilProvider.getBlockNumber();
|
|
||||||
const targetBlock = currentBlock - 10;
|
|
||||||
|
|
||||||
|
|
||||||
// Fetch pool key from Position Manager
|
|
||||||
let poolKey;
|
|
||||||
try {
|
|
||||||
poolKey = await getPoolKey();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch pool key. Exiting.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get pool price data
|
|
||||||
let poolPriceData;
|
|
||||||
try {
|
|
||||||
poolPriceData = await getPoolPriceWithSDK(targetBlock);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to get pool price data:', error.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get quotes for both directions
|
|
||||||
try {
|
|
||||||
const quoteResults = await getQuoteFromHistoricalBlock(poolKey, targetBlock);
|
|
||||||
console.log('✅ Historical quote successful!');
|
|
||||||
|
|
||||||
// Write results to CSV
|
|
||||||
writeResultsToCSV(targetBlock, poolPriceData, quoteResults);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to get historical quote:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the main function
|
|
||||||
main().catch(console.error);
|
|
||||||
@@ -7,6 +7,19 @@ import numpy as np
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
LMSR_FEE = 0.0025
|
||||||
|
# UNISWAP_GAS=0
|
||||||
|
# LMSR_GAS=0
|
||||||
|
UNISWAP_GAS=115_000
|
||||||
|
LMSR_GAS=150_000
|
||||||
|
ETH_PRICE=4000
|
||||||
|
UNISWAP_GAS_COST=UNISWAP_GAS*ETH_PRICE/1e9
|
||||||
|
LMSR_GAS_COST=LMSR_GAS*ETH_PRICE/1e9
|
||||||
|
|
||||||
|
|
||||||
|
print(f' LMSR gas: ${LMSR_GAS_COST:.2}')
|
||||||
|
print(f'Uniswap gas: ${UNISWAP_GAS_COST:.2}')
|
||||||
|
|
||||||
|
|
||||||
def lmsr_swap_amount_out(
|
def lmsr_swap_amount_out(
|
||||||
balances,
|
balances,
|
||||||
@@ -84,9 +97,9 @@ def lmsr_swap_amount_out(
|
|||||||
# No available output to withdraw
|
# No available output to withdraw
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
# Compute r0 = exp((q_i - q_j) / b)
|
# Compute r0 = exp((q_j - q_i) / b) so small-trade out/in ≈ marginal price p_j/p_i
|
||||||
try:
|
try:
|
||||||
r0 = math.exp((qi - qj) / b)
|
r0 = math.exp((qj - qi) / b)
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
raise ArithmeticError("exponential overflow in r0 computation")
|
raise ArithmeticError("exponential overflow in r0 computation")
|
||||||
|
|
||||||
@@ -126,37 +139,124 @@ def lmsr_swap_amount_out(
|
|||||||
|
|
||||||
return float(amount_out)
|
return float(amount_out)
|
||||||
|
|
||||||
|
def lmsr_marginal_price(balances, base_index, quote_index, kappa):
|
||||||
|
"""
|
||||||
|
Compute the LMSR marginal price ratio p_quote / p_base for the given balances state.
|
||||||
|
|
||||||
def main():
|
Formula:
|
||||||
balance0 = 1_000_000 # estimated from the production pool
|
b = kappa * S, where S = sum(balances)
|
||||||
balances = [balance0, balance0]
|
price = exp((q_quote - q_base) / b)
|
||||||
X = np.geomspace(1, 10_000_000, 100)
|
|
||||||
Y = [1 -
|
|
||||||
lmsr_swap_amount_out(balances, float(amount_in), 0, 1, 0.000010, 0.8)
|
|
||||||
/ amount_in
|
|
||||||
for amount_in in X]
|
|
||||||
plt.plot(X / balance0, Y, label='LMSR')
|
|
||||||
|
|
||||||
d = pd.read_csv('swap_results_block_23640998.csv')
|
Parameters:
|
||||||
d.columns = ['block', 'price0', 'price1', 'in0', 'out0', 'in1', 'out1']
|
- balances: iterable of per-token balances (q_i)
|
||||||
uniswap_slippage = 1 - d.out0 / d.in0 / d.iloc[0].price0
|
- base_index: index of the base token
|
||||||
plt.plot(d.in0 / balance0, uniswap_slippage, label='CP')
|
- quote_index: index of the quote token
|
||||||
|
- kappa: liquidity parameter κ (must be positive)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- float: marginal price p_quote / p_base
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
q = [float(x) for x in balances]
|
||||||
|
k = float(kappa)
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
raise ValueError("Invalid numeric input") from e
|
||||||
|
|
||||||
|
n = len(q)
|
||||||
|
if not (0 <= base_index < n and 0 <= quote_index < n):
|
||||||
|
raise IndexError("token indices out of range")
|
||||||
|
if k <= 0.0:
|
||||||
|
raise ValueError("kappa must be positive")
|
||||||
|
|
||||||
|
S = sum(q)
|
||||||
|
if S <= 0.0:
|
||||||
|
raise ValueError("size metric (sum balances) must be positive")
|
||||||
|
|
||||||
|
b = k * S
|
||||||
|
if b <= 0.0:
|
||||||
|
raise ValueError("computed b must be positive")
|
||||||
|
|
||||||
|
return float(math.exp((q[quote_index] - q[base_index]) / b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def compare(file, tvl, kappa):
|
||||||
|
d = pd.read_csv(file)
|
||||||
|
d.columns = ['block', 'price0', 'price1', 'in0', 'out0', 'rate']
|
||||||
|
|
||||||
|
# Calibrate LMSR balances so that exp((q1 - q0)/b) equals the initial price
|
||||||
|
p0 = float(d.iloc[0].price0)
|
||||||
|
S = float(tvl) # choose the LMSR size metric
|
||||||
|
b = kappa * S
|
||||||
|
delta = b * math.log(p0) # q1 - q0
|
||||||
|
q0 = 0.5 * (S - delta)
|
||||||
|
q1 = 0.5 * (S + delta)
|
||||||
|
if q0 <= 0.0 or q1 <= 0.0:
|
||||||
|
raise ValueError("Invalid LMSR calibration: choose kappa such that kappa * ln(price0) < 1.")
|
||||||
|
balances = [q0, q1]
|
||||||
|
print(balances)
|
||||||
|
X = np.geomspace(1, 1_000_000, 100)
|
||||||
|
orig_price = lmsr_marginal_price(balances, 0, 1, kappa)
|
||||||
|
in_out = [(float(amount_in), lmsr_swap_amount_out(balances, float(amount_in), 0, 1, LMSR_FEE, kappa)) for amount_in in X]
|
||||||
|
print(in_out)
|
||||||
|
# Relative execution price deviation from the initial marginal price:
|
||||||
|
# slippage = |(amount_out/amount_in)/orig_price - 1|
|
||||||
|
eps = 1e-12
|
||||||
|
Y = [max(eps, abs((amount_out / amount_in) / orig_price - 1.0))
|
||||||
|
for amount_in, amount_out in in_out]
|
||||||
|
plt.plot(X, Y, label=f'LMSR {LMSR_FEE:.2%} κ={kappa:.2f}', color='cornflowerblue')
|
||||||
|
|
||||||
|
# Uniswap execution price deviation from its initial quoted price:
|
||||||
|
# slippage = |(out/in)/initial_price - 1|
|
||||||
|
uniswap_exec_price0 = d.out0 / d.in0
|
||||||
|
uniswap_slippage0 = (uniswap_exec_price0 / d.iloc[0].price0 - 1.0).abs().clip(lower=1e-12)
|
||||||
|
uniswap_fee = round(uniswap_slippage0.iloc[0], 6)
|
||||||
|
plt.plot(d.in0, uniswap_slippage0, label=f'Uniswap {uniswap_fee:.2%}', color='hotpink')
|
||||||
|
# uniswap_slippage1 = |(out1/in1)/price1 - 1|
|
||||||
|
# plt.plot(d.in1, (d.out1 / d.in1 / d.iloc[0].price1 - 1.0).abs().clip(lower=1e-12), label='CP1')
|
||||||
|
|
||||||
# Interpolate Uniswap slippage to match LMSR x-coordinates
|
# Interpolate Uniswap slippage to match LMSR x-coordinates
|
||||||
interp_uniswap = np.interp(X / balance0, d.in0 / balance0, uniswap_slippage)
|
interp_uniswap = np.interp(X, d.in0, uniswap_slippage0)
|
||||||
mask = Y < interp_uniswap
|
mask = Y < interp_uniswap
|
||||||
plt.fill_between(X / balance0, 0, 1, where=mask, alpha=0.2, color='green')
|
plt.fill_between(X, 0, 1, where=mask, alpha=0.2, color='green')
|
||||||
|
|
||||||
plt.xscale('log')
|
plt.xscale('log')
|
||||||
plt.yscale('log')
|
plt.yscale('log')
|
||||||
plt.gca().xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: '{:.2f}'.format(10000*x)))
|
plt.gca().xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: '{:g}'.format(x)))
|
||||||
plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: '{:.2f}%'.format(y)))
|
plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: '{:.2%}'.format(y)))
|
||||||
plt.xlabel('Input Amount (basis points of initial balance)')
|
plt.gca().set_ylim(top=.1)
|
||||||
|
plt.xlabel('Input Amount')
|
||||||
plt.ylabel('Slippage')
|
plt.ylabel('Slippage')
|
||||||
plt.title('Pool Slippages')
|
plt.title('Pool Slippages')
|
||||||
plt.grid(True)
|
plt.grid(True)
|
||||||
plt.legend()
|
plt.legend()
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_kappa():
|
||||||
|
X = np.geomspace(1, 10_000_000, 100)
|
||||||
|
for kappa in np.geomspace(0.01, 100, 8):
|
||||||
|
balance0 = 1_000_000 # estimated from the production pool
|
||||||
|
balances = [balance0, balance0]
|
||||||
|
Y = [1 -
|
||||||
|
lmsr_swap_amount_out(balances, float(amount_in), 0, 1, 0.000010, kappa)
|
||||||
|
/ amount_in
|
||||||
|
for amount_in in X]
|
||||||
|
plt.plot(X / balance0, Y, label=f'{kappa:f}')
|
||||||
|
plt.xscale('log')
|
||||||
|
plt.yscale('log')
|
||||||
|
plt.gca().xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: '{:.2f}'.format(10000*x)))
|
||||||
|
plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: '{:.2%}'.format(y)))
|
||||||
|
plt.xlabel('Input Amount (basis points of initial balance)')
|
||||||
|
plt.ylabel('Slippage')
|
||||||
|
plt.title('Pool Slippages by Kappa')
|
||||||
|
plt.grid(True)
|
||||||
|
plt.legend()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
# compare('uni4_quotes/swap_results_block_23640998.csv')
|
||||||
|
# compare('uni4_quotes/ETH-USDC-30.csv', 53_000_000, 0.1)
|
||||||
|
compare('uni4_quotes/ETH-USDC-30.csv', 1_00_000, .1)
|
||||||
|
# plot_kappa()
|
||||||
|
|||||||
496
research/uni4_quotes/get_quotes.js
Normal file
496
research/uni4_quotes/get_quotes.js
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { Token } from '@uniswap/sdk-core';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
// Token definitions
|
||||||
|
const ChainId = {
|
||||||
|
MAINNET: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const USDC_TOKEN = new Token(
|
||||||
|
ChainId.MAINNET,
|
||||||
|
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||||
|
6,
|
||||||
|
'USDC',
|
||||||
|
'USDC'
|
||||||
|
);
|
||||||
|
|
||||||
|
const WETH_TOKEN = new Token(
|
||||||
|
ChainId.MAINNET,
|
||||||
|
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||||
|
18,
|
||||||
|
'WETH',
|
||||||
|
'WETH'
|
||||||
|
);
|
||||||
|
|
||||||
|
const USDT_TOKEN= new Token(
|
||||||
|
ChainId.MAINNET,
|
||||||
|
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||||
|
6,
|
||||||
|
'USDT',
|
||||||
|
'USDT'
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokenA = USDC_TOKEN
|
||||||
|
const tokenB = WETH_TOKEN
|
||||||
|
// Pool ID to fetch pool key from
|
||||||
|
// const POOL_ID = '0x8aa4e11cbdf30eedc92100f4c8a31ff748e201d44712cc8c90d189edaa8e4e47';
|
||||||
|
const POOL_ID = '0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d';
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const QUOTER_ADDRESS = '0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203';
|
||||||
|
const POSITION_MANAGER_ADDRESS = '0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e';
|
||||||
|
|
||||||
|
|
||||||
|
// Providers
|
||||||
|
const anvilProvider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545');
|
||||||
|
|
||||||
|
// Quoter contract ABI
|
||||||
|
const QUOTER_ABI = [
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
components: [
|
||||||
|
{ name: 'currency0', type: 'address' },
|
||||||
|
{ name: 'currency1', type: 'address' },
|
||||||
|
{ name: 'fee', type: 'uint24' },
|
||||||
|
{ name: 'tickSpacing', type: 'int24' },
|
||||||
|
{ name: 'hooks', type: 'address' }
|
||||||
|
],
|
||||||
|
name: 'poolKey',
|
||||||
|
type: 'tuple'
|
||||||
|
},
|
||||||
|
{ name: 'zeroForOne', type: 'bool' },
|
||||||
|
{ name: 'exactAmount', type: 'uint128' },
|
||||||
|
{ name: 'hookData', type: 'bytes' }
|
||||||
|
],
|
||||||
|
name: 'params',
|
||||||
|
type: 'tuple'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
name: 'quoteExactInputSingle',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
components: [
|
||||||
|
{ name: 'amountOut', type: 'uint256' }
|
||||||
|
],
|
||||||
|
name: 'result',
|
||||||
|
type: 'tuple'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// StateView ABI for getSlot0 and getLiquidity
|
||||||
|
const STATE_VIEW_ABI = [
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
// { name: 'manager', type: 'address' },
|
||||||
|
{ name: 'poolId', type: 'bytes32' }
|
||||||
|
],
|
||||||
|
name: 'getSlot0',
|
||||||
|
outputs: [
|
||||||
|
{ name: 'sqrtPriceX96', type: 'uint160' },
|
||||||
|
{ name: 'tick', type: 'int24' },
|
||||||
|
{ name: 'protocolFee', type: 'uint24' },
|
||||||
|
{ name: 'lpFee', type: 'uint24' }
|
||||||
|
],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// StateView contract address for reading pool state
|
||||||
|
const STATE_VIEW_ADDRESS = '0x7ffe42c4a5deea5b0fec41c94c136cf115597227';
|
||||||
|
|
||||||
|
// Position Manager ABI
|
||||||
|
const POSITION_MANAGER_ABI = [
|
||||||
|
{
|
||||||
|
inputs: [{ name: 'poolId', type: 'bytes25' }],
|
||||||
|
name: 'poolKeys',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
components: [
|
||||||
|
{ name: 'currency0', type: 'address' },
|
||||||
|
{ name: 'currency1', type: 'address' },
|
||||||
|
{ name: 'fee', type: 'uint24' },
|
||||||
|
{ name: 'tickSpacing', type: 'int24' },
|
||||||
|
{ name: 'hooks', type: 'address' }
|
||||||
|
],
|
||||||
|
name: 'poolKey',
|
||||||
|
type: 'tuple'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Function to get pool key from Position Manager (uses mainnet)
|
||||||
|
async function getPoolKey() {
|
||||||
|
try {
|
||||||
|
const positionManager = new ethers.Contract(
|
||||||
|
POSITION_MANAGER_ADDRESS,
|
||||||
|
POSITION_MANAGER_ABI,
|
||||||
|
anvilProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract first 25 bytes (50 hex chars + 0x prefix = 52 chars)
|
||||||
|
const poolIdBytes25 = POOL_ID.slice(0, 52);
|
||||||
|
|
||||||
|
const poolKey = await positionManager.poolKeys(poolIdBytes25);
|
||||||
|
|
||||||
|
return {
|
||||||
|
currency0: poolKey.currency0,
|
||||||
|
currency1: poolKey.currency1,
|
||||||
|
fee: poolKey.fee,
|
||||||
|
tickSpacing: poolKey.tickSpacing,
|
||||||
|
hooks: poolKey.hooks
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching pool key:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format BigNumber for display (use only for console output)
|
||||||
|
function formatBigNumberForDisplay(bigNumber, decimals) {
|
||||||
|
const formatted = ethers.utils.formatUnits(bigNumber, decimals);
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to convert BigNumber to fixed decimal string without scientific notation
|
||||||
|
function bigNumberToFixed(bigNumber, decimals) {
|
||||||
|
const str = ethers.utils.formatUnits(bigNumber, decimals);
|
||||||
|
// Ensure no scientific notation
|
||||||
|
if (str.includes('e')) {
|
||||||
|
const num = parseFloat(str);
|
||||||
|
return num.toFixed(decimals);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert sqrtPriceX96 to price as a decimal string (maintains precision)
|
||||||
|
// Returns price representing token1/token0
|
||||||
|
function sqrtPriceX96ToPrice(sqrtPriceX96, decimals0, decimals1) {
|
||||||
|
const sqrtPrice = ethers.BigNumber.from(sqrtPriceX96);
|
||||||
|
|
||||||
|
console.log('DEBUG sqrtPriceX96ToPrice:');
|
||||||
|
console.log(' sqrtPriceX96:', sqrtPriceX96.toString());
|
||||||
|
console.log(' decimals0:', decimals0, 'decimals1:', decimals1);
|
||||||
|
|
||||||
|
if (sqrtPrice.isZero()) {
|
||||||
|
throw new Error('sqrtPriceX96 cannot be zero');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate price = (sqrtPriceX96 / 2^96)^2 * 10^(decimals0 - decimals1)
|
||||||
|
// Using BigNumber arithmetic throughout to maintain precision
|
||||||
|
|
||||||
|
const Q96 = ethers.BigNumber.from(2).pow(96);
|
||||||
|
|
||||||
|
// price = sqrtPrice^2 / 2^192 * 10^(decimals0 - decimals1)
|
||||||
|
// To maintain precision, we need to be careful about the order of operations
|
||||||
|
|
||||||
|
// First square the sqrtPrice
|
||||||
|
const sqrtPriceSquared = sqrtPrice.mul(sqrtPrice);
|
||||||
|
console.log(' sqrtPriceSquared:', sqrtPriceSquared.toString());
|
||||||
|
|
||||||
|
// Calculate 2^192
|
||||||
|
const Q192 = Q96.mul(Q96);
|
||||||
|
console.log(' Q192:', Q192.toString());
|
||||||
|
|
||||||
|
// Adjust for decimals: if decimals0 > decimals1, we multiply, else divide
|
||||||
|
const decimalDiff = decimals0 - decimals1;
|
||||||
|
console.log(' decimalDiff:', decimalDiff);
|
||||||
|
|
||||||
|
// We need to calculate: sqrtPriceSquared * 10^decimalDiff / Q192
|
||||||
|
// Use a large precision scale to avoid truncation
|
||||||
|
// We'll use 30 decimals to ensure we have enough precision even for extreme price ratios
|
||||||
|
|
||||||
|
const PRECISION_DECIMALS = 30;
|
||||||
|
const precisionScale = ethers.BigNumber.from(10).pow(PRECISION_DECIMALS);
|
||||||
|
let price;
|
||||||
|
|
||||||
|
if (decimalDiff >= 0) {
|
||||||
|
const decimalAdjustment = ethers.BigNumber.from(10).pow(decimalDiff);
|
||||||
|
const numerator = sqrtPriceSquared.mul(decimalAdjustment).mul(precisionScale);
|
||||||
|
console.log(' numerator:', numerator.toString());
|
||||||
|
price = numerator.div(Q192);
|
||||||
|
} else {
|
||||||
|
// When decimalDiff is negative, multiply denominator instead of dividing numerator
|
||||||
|
// This avoids precision loss
|
||||||
|
const decimalAdjustment = ethers.BigNumber.from(10).pow(-decimalDiff);
|
||||||
|
const numerator = sqrtPriceSquared.mul(precisionScale);
|
||||||
|
console.log(' numerator:', numerator.toString());
|
||||||
|
const denominator = Q192.mul(decimalAdjustment);
|
||||||
|
console.log(' denominator:', denominator.toString());
|
||||||
|
price = numerator.div(denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(' price (raw):', price.toString());
|
||||||
|
|
||||||
|
const priceFormatted = ethers.utils.formatUnits(price, PRECISION_DECIMALS);
|
||||||
|
console.log(' price (formatted):', priceFormatted);
|
||||||
|
|
||||||
|
return priceFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to calculate inverse price from a decimal string
|
||||||
|
function inversePrice(priceStr) {
|
||||||
|
const priceNum = parseFloat(priceStr);
|
||||||
|
if (priceNum === 0 || !isFinite(priceNum)) {
|
||||||
|
console.warn('Warning: Cannot calculate inverse of zero or invalid price');
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate 1/price using standard division
|
||||||
|
const inverse = 1.0 / priceNum;
|
||||||
|
|
||||||
|
// If result is very large, format as integer-like
|
||||||
|
// If result is normal decimal, format with precision
|
||||||
|
if (inverse > 1e10) {
|
||||||
|
return inverse.toExponential(18).replace(/e\+?/, 'e');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format with high precision, removing trailing zeros
|
||||||
|
let formatted = inverse.toFixed(18);
|
||||||
|
// Remove trailing zeros but keep at least one decimal place
|
||||||
|
formatted = formatted.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '.0');
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get pool price using StateView
|
||||||
|
async function getPoolPriceWithSDK(blockNumber) {
|
||||||
|
try {
|
||||||
|
// Create StateView contract with ethers
|
||||||
|
const stateViewContract = new ethers.Contract(
|
||||||
|
STATE_VIEW_ADDRESS,
|
||||||
|
STATE_VIEW_ABI,
|
||||||
|
anvilProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get slot0 data
|
||||||
|
const slot0 = await stateViewContract.getSlot0(POOL_ID, {
|
||||||
|
blockTag: blockNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert sqrtPriceX96 to price as BigNumber (maintains precision)
|
||||||
|
console.log('\n=== Pool State (Debug) ===');
|
||||||
|
console.log('Block:', blockNumber);
|
||||||
|
console.log('sqrtPriceX96:', slot0.sqrtPriceX96.toString());
|
||||||
|
console.log('Tick:', slot0.tick.toString());
|
||||||
|
console.log('Token A decimals:', tokenA.decimals);
|
||||||
|
console.log('Token B decimals:', tokenB.decimals);
|
||||||
|
|
||||||
|
// Calculate price: token0/token1 = USDC/WETH
|
||||||
|
// Using swapped decimals gives us the correct direction
|
||||||
|
const token0PerToken1 = sqrtPriceX96ToPrice(slot0.sqrtPriceX96, tokenB.decimals, tokenA.decimals);
|
||||||
|
console.log('Price USDC per WETH:', token0PerToken1);
|
||||||
|
|
||||||
|
// Calculate the reciprocal to get WETH per USDC
|
||||||
|
const token0PerToken1Num = parseFloat(token0PerToken1);
|
||||||
|
const token1PerToken0 = (1.0 / token0PerToken1Num).toFixed(18).replace(/\.?0+$/, '');
|
||||||
|
console.log('Price WETH per USDC (reciprocal):', token1PerToken0);
|
||||||
|
|
||||||
|
console.log('\n=== Pool State ===');
|
||||||
|
console.log('Block:', blockNumber);
|
||||||
|
console.log('sqrtPriceX96:', slot0.sqrtPriceX96.toString());
|
||||||
|
console.log('Tick:', slot0.tick.toString());
|
||||||
|
console.log(`Price calculated as: token1/token0 where token0=${tokenA.symbol}(${tokenA.decimals}d), token1=${tokenB.symbol}(${tokenB.decimals}d)`);
|
||||||
|
console.log(`${tokenB.symbol} per ${tokenA.symbol}:`, token1PerToken0);
|
||||||
|
console.log(`${tokenA.symbol} per ${tokenB.symbol}:`, token0PerToken1);
|
||||||
|
console.log('LP Fee:', slot0.lpFee, `(${(slot0.lpFee / 10000).toFixed(2)}%)`);
|
||||||
|
|
||||||
|
// Sanity check with actual swap data
|
||||||
|
console.log('\nSanity check: If swapping 1 USDC should get ~0.000256 WETH based on your data');
|
||||||
|
|
||||||
|
return {
|
||||||
|
sqrtPriceX96: slot0.sqrtPriceX96.toString(),
|
||||||
|
tick: slot0.tick.toString(),
|
||||||
|
priceStr: token1PerToken0, // WETH per USDC (tokenB per tokenA)
|
||||||
|
inversePriceStr: token0PerToken1, // USDC per WETH (tokenA per tokenB)
|
||||||
|
protocolFee: slot0.protocolFee,
|
||||||
|
lpFee: slot0.lpFee
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting pool price:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get quote from historical block (10 blocks back) with multiple amount testing
|
||||||
|
async function getQuoteFromHistoricalBlock(poolKey, targetBlock) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Get starting pool price using SDK
|
||||||
|
try {
|
||||||
|
await getPoolPriceWithSDK(targetBlock);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting pool price with SDK:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n=== Testing Multiple Input Amounts ===');
|
||||||
|
console.log('Testing amounts from 1 USDC, increasing by 30% each iteration (limited to 5 iterations)');
|
||||||
|
console.log('Testing both directions: USDC→USDT and USDT→USDC\n');
|
||||||
|
|
||||||
|
// Create a new provider instance that queries at specific block
|
||||||
|
const quoterContract = new ethers.Contract(QUOTER_ADDRESS, QUOTER_ABI, anvilProvider);
|
||||||
|
|
||||||
|
const resultsForward = []; // USDC→USDT
|
||||||
|
const resultsReverse = []; // USDT→USDC
|
||||||
|
|
||||||
|
// Start with 1 token as BigNumber
|
||||||
|
let currentAmountBN = ethers.utils.parseUnits("1", tokenA.decimals);
|
||||||
|
const multiplierNumerator = 13; // 130% = 13/10
|
||||||
|
const multiplierDenominator = 10;
|
||||||
|
const maxIterations = 200;
|
||||||
|
const maxAmount = ethers.utils.parseUnits("1000000", tokenA.decimals); // 10 million limit
|
||||||
|
|
||||||
|
// Test USDC→USDT (forward direction)
|
||||||
|
console.log('\n=== USDC → USDT (Forward Direction) ===');
|
||||||
|
for (let i = 0; i < maxIterations; i++) {
|
||||||
|
// Check if currentAmountBN exceeds 10 million
|
||||||
|
if (currentAmountBN.gt(maxAmount)) {
|
||||||
|
console.log(`\nReached maximum amount limit of 10 million USDC. Stopping iterations.`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentAmountFormatted = formatBigNumberForDisplay(currentAmountBN, tokenA.decimals);
|
||||||
|
console.log(`\n--- Iteration ${i + 1}: ${currentAmountFormatted} USDC ---`);
|
||||||
|
|
||||||
|
const amountIn = currentAmountBN;
|
||||||
|
|
||||||
|
// Make the call at the specific block using overrides
|
||||||
|
const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle({
|
||||||
|
poolKey: poolKey,
|
||||||
|
zeroForOne: tokenA.address.toLowerCase() > tokenB.address.toLowerCase(),
|
||||||
|
exactAmount: amountIn.toString(),
|
||||||
|
hookData: '0x00',
|
||||||
|
}, {
|
||||||
|
blockTag: targetBlock // Query at specific historical block
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('in/out', amountIn, quotedAmountOut.amountIn, quotedAmountOut.amountOut);
|
||||||
|
const actualAmountOut = ethers.BigNumber.from(quotedAmountOut.amountOut);
|
||||||
|
|
||||||
|
// Calculate effective exchange rate as BigNumber (with extra precision)
|
||||||
|
// effectiveRate = actualAmountOut / amountIn
|
||||||
|
// Scale to show tokenB per 1 tokenA (use tokenB decimals for result)
|
||||||
|
const scaleFactor = ethers.BigNumber.from(10).pow(tokenB.decimals);
|
||||||
|
const effectiveRateBN = actualAmountOut.mul(scaleFactor).div(amountIn);
|
||||||
|
|
||||||
|
// Log results for this amount (format only for display)
|
||||||
|
console.log(`Amount In: ${currentAmountFormatted} ${tokenA.symbol}`);
|
||||||
|
console.log(`Amount Out: ${formatBigNumberForDisplay(actualAmountOut, tokenB.decimals)} ${tokenB.symbol}`);
|
||||||
|
|
||||||
|
// Store result with full precision (as strings)
|
||||||
|
resultsForward.push({
|
||||||
|
iteration: i + 1,
|
||||||
|
amountInBN: amountIn.toString(),
|
||||||
|
amountOutBN: actualAmountOut.toString(),
|
||||||
|
effectiveRateBN: effectiveRateBN.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Increase amount by 30% for next iteration using BigNumber math
|
||||||
|
currentAmountBN = currentAmountBN.mul(multiplierNumerator).div(multiplierDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
console.log('\n\n=== Summary of Results ===');
|
||||||
|
|
||||||
|
console.log(`--- Forward Direction (${tokenA.symbol} → ${tokenB.symbol}) ---`);
|
||||||
|
console.log('\nAmount In → Amount Out (Rate) [Slippage]');
|
||||||
|
resultsForward.forEach(r => {
|
||||||
|
const amountIn = formatBigNumberForDisplay(ethers.BigNumber.from(r.amountInBN), tokenA.decimals);
|
||||||
|
const amountOut = formatBigNumberForDisplay(ethers.BigNumber.from(r.amountOutBN), tokenB.decimals);
|
||||||
|
const rate = formatBigNumberForDisplay(ethers.BigNumber.from(r.effectiveRateBN), tokenB.decimals);
|
||||||
|
console.log(`${amountIn} ${tokenA.symbol} → ${amountOut} ${tokenB.symbol} (${rate})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
blockNumber: targetBlock,
|
||||||
|
forward: resultsForward,
|
||||||
|
reverse: resultsReverse
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting historical quote:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to write results to CSV with full precision
|
||||||
|
function writeResultsToCSV(blockNumber, priceData, quoteResults) {
|
||||||
|
const filename = `swap_results_block_${blockNumber}.csv`;
|
||||||
|
|
||||||
|
// Create CSV header
|
||||||
|
let csvContent = `block,${tokenB.symbol} per ${tokenA.symbol},${tokenA.symbol} per ${tokenB.symbol},Amount In,Amount Out,Effective Rate\n`;
|
||||||
|
|
||||||
|
// Get max number of iterations
|
||||||
|
const maxIterations = quoteResults.forward.length;
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for (let i = 0; i < maxIterations; i++) {
|
||||||
|
const forwardResult = quoteResults.forward[i];
|
||||||
|
|
||||||
|
// Use full precision for prices (no scientific notation)
|
||||||
|
const priceStr = priceData.priceStr;
|
||||||
|
const inversePriceStr = priceData.inversePriceStr;
|
||||||
|
csvContent += `${blockNumber},${priceStr},${inversePriceStr},`;
|
||||||
|
|
||||||
|
// Forward direction data with full precision
|
||||||
|
const amountInFormatted = bigNumberToFixed(ethers.BigNumber.from(forwardResult.amountInBN), tokenA.decimals);
|
||||||
|
const amountOutFormatted = bigNumberToFixed(ethers.BigNumber.from(forwardResult.amountOutBN), tokenB.decimals);
|
||||||
|
const effectiveRate = bigNumberToFixed(ethers.BigNumber.from(forwardResult.effectiveRateBN), tokenB.decimals);
|
||||||
|
|
||||||
|
csvContent += `${amountInFormatted},${amountOutFormatted},${effectiveRate}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
fs.writeFileSync(filename, csvContent);
|
||||||
|
console.log(`\n✅ Results written to ${filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
async function main() {
|
||||||
|
console.log('=== Uniswap V4 Quote and Gas Estimation ===\n');
|
||||||
|
// Get current block
|
||||||
|
const currentBlock = await anvilProvider.getBlockNumber();
|
||||||
|
const targetBlock = currentBlock - 10;
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch pool key from Position Manager
|
||||||
|
let poolKey;
|
||||||
|
try {
|
||||||
|
poolKey = await getPoolKey();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch pool key. Exiting.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get pool price data
|
||||||
|
let poolPriceData;
|
||||||
|
try {
|
||||||
|
poolPriceData = await getPoolPriceWithSDK(targetBlock);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get pool price data:', error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get quotes for both directions
|
||||||
|
try {
|
||||||
|
const quoteResults = await getQuoteFromHistoricalBlock(poolKey, targetBlock);
|
||||||
|
console.log('✅ Historical quote successful!');
|
||||||
|
|
||||||
|
// Write results to CSV
|
||||||
|
writeResultsToCSV(targetBlock, poolPriceData, quoteResults);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to get historical quote:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the main function
|
||||||
|
main().catch(console.error);
|
||||||
18
research/uni4_quotes/package.json
Normal file
18
research/uni4_quotes/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "uni4_quotes",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"author": "",
|
||||||
|
"type": "module",
|
||||||
|
"main": "get_quotes.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node get_quotes.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@uniswap/sdk-core": "^7.8.0",
|
||||||
|
"@uniswap/v4-sdk": "^1.22.0",
|
||||||
|
"ethers": "^5.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,9 +117,9 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
|||||||
/// @notice Callable by anyone, sends any owed protocol fees to the protocol fee address.
|
/// @notice Callable by anyone, sends any owed protocol fees to the protocol fee address.
|
||||||
function collectProtocolFees() external;
|
function collectProtocolFees() external;
|
||||||
|
|
||||||
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
/// @notice Fixed LMSR curvature parameter b (Q64.64) Larger values allow more liquidity to be taken with less price impact.
|
||||||
/// @dev Pools are constructed with a κ value; this getter exposes the κ used by the pool.
|
/// @dev Pools are constructed with a fixed b; this getter exposes the b used by the pool.
|
||||||
function kappa() external view returns (int128);
|
function bFixed() external view returns (int128);
|
||||||
|
|
||||||
/// @notice If a security problem is found, the vault owner may call this function to permanently disable swap and
|
/// @notice If a security problem is found, the vault owner may call this function to permanently disable swap and
|
||||||
/// mint functionality, leaving only burns (withdrawals) working.
|
/// mint functionality, leaving only burns (withdrawals) working.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ library LMSRStabilized {
|
|||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
uint256 nAssets;
|
uint256 nAssets;
|
||||||
int128 kappa; // liquidity parameter κ (64.64 fixed point)
|
int128 bFixed; // fixed b curvature (Q64.64)
|
||||||
int128[] qInternal; // cached internal balances in 64.64 fixed-point format
|
int128[] qInternal; // cached internal balances in 64.64 fixed-point format
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,11 +26,10 @@ library LMSRStabilized {
|
|||||||
function init(
|
function init(
|
||||||
State storage s,
|
State storage s,
|
||||||
int128[] memory initialQInternal,
|
int128[] memory initialQInternal,
|
||||||
int128 kappa
|
int128 bFixed
|
||||||
) internal {
|
) internal {
|
||||||
s.nAssets = initialQInternal.length;
|
s.nAssets = initialQInternal.length;
|
||||||
|
|
||||||
// Initialize qInternal cache
|
|
||||||
if (s.qInternal.length != initialQInternal.length) {
|
if (s.qInternal.length != initialQInternal.length) {
|
||||||
s.qInternal = new int128[](initialQInternal.length);
|
s.qInternal = new int128[](initialQInternal.length);
|
||||||
}
|
}
|
||||||
@@ -42,9 +41,73 @@ library LMSRStabilized {
|
|||||||
int128 total = _computeSizeMetric(s.qInternal);
|
int128 total = _computeSizeMetric(s.qInternal);
|
||||||
require(total > int128(0), "LMSR: total zero");
|
require(total > int128(0), "LMSR: total zero");
|
||||||
|
|
||||||
// Set kappa directly (caller provides kappa)
|
require(bFixed > int128(0), "LMSR: b<=0");
|
||||||
s.kappa = kappa;
|
s.bFixed = bFixed;
|
||||||
require(s.kappa > int128(0), "LMSR: kappa>0");
|
}
|
||||||
|
|
||||||
|
/* --------------------
|
||||||
|
Virtual offset helpers (pure) for initialization
|
||||||
|
-------------------- */
|
||||||
|
|
||||||
|
/// @notice Compute per-asset virtual offsets v so that the initial marginal price between `base` and `quote`
|
||||||
|
/// equals `targetPrice` (Q64.64). Returns an array v with v_base and v_quote adjusted and others zero.
|
||||||
|
/// @dev Given reserves r, we want s = r + v with s_quote - s_base = b * ln(targetPrice).
|
||||||
|
/// Let deltaDesired = b*ln(targetPrice), deltaCurrent = r_quote - r_base, then
|
||||||
|
/// let adj = deltaDesired - deltaCurrent; choose v_base = -adj/2, v_quote = adj/2, others 0.
|
||||||
|
function computeOffsetsForPricePair(
|
||||||
|
int128 b,
|
||||||
|
int128[] memory reservesInternal,
|
||||||
|
uint256 baseIndex,
|
||||||
|
uint256 quoteIndex,
|
||||||
|
int128 targetPrice
|
||||||
|
) internal pure returns (int128[] memory vOffsets) {
|
||||||
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
|
uint256 n = reservesInternal.length;
|
||||||
|
require(baseIndex < n && quoteIndex < n, "LMSR: idx");
|
||||||
|
|
||||||
|
vOffsets = new int128[](n);
|
||||||
|
|
||||||
|
// adj = b * ln(targetPrice) - (r_quote - r_base)
|
||||||
|
int128 deltaDesired = b.mul(_ln(targetPrice));
|
||||||
|
int128 deltaCurrent = reservesInternal[quoteIndex].sub(reservesInternal[baseIndex]);
|
||||||
|
int128 adj = deltaDesired.sub(deltaCurrent);
|
||||||
|
|
||||||
|
int128 two = ABDKMath64x64.fromUInt(2);
|
||||||
|
// v_base = -adj/2; v_quote = adj/2
|
||||||
|
vOffsets[baseIndex] = adj.neg().div(two);
|
||||||
|
vOffsets[quoteIndex] = adj.div(two);
|
||||||
|
return vOffsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Compute per-asset virtual offsets v to match a vector of relative log-prices (Q64.64).
|
||||||
|
/// @dev logPrices[i] should represent ln(p_i / p_ref) for a chosen reference asset ref (e.g., ref = 0 => logPrices[0] = 0).
|
||||||
|
/// We set v_0 = 0 and for i>0 require (v_i - v_0) = b*(logPrices[i] - logPrices[0]) - (r_i - r_0).
|
||||||
|
/// Note: adding a constant to all v does not change prices; callers may shift v uniformly if they want S>0 margin.
|
||||||
|
function computeOffsetsForLogPrices(
|
||||||
|
int128 b,
|
||||||
|
int128[] memory reservesInternal,
|
||||||
|
uint256 referenceIndex,
|
||||||
|
int128[] memory logPrices
|
||||||
|
) internal pure returns (int128[] memory vOffsets) {
|
||||||
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
|
uint256 n = reservesInternal.length;
|
||||||
|
require(logPrices.length == n, "LMSR: length mismatch");
|
||||||
|
require(referenceIndex < n, "LMSR: ref idx");
|
||||||
|
|
||||||
|
vOffsets = new int128[](n);
|
||||||
|
|
||||||
|
// Set v_ref = 0, solve differences for others
|
||||||
|
uint256 ref = referenceIndex;
|
||||||
|
int128 logP_ref = logPrices[ref];
|
||||||
|
for (uint256 i = 0; i < n; ) {
|
||||||
|
if (i != ref) {
|
||||||
|
int128 desiredDiff = b.mul(logPrices[i].sub(logP_ref)); // b*(ln p_i - ln p_ref)
|
||||||
|
int128 currentDiff = reservesInternal[i].sub(reservesInternal[ref]); // r_i - r_ref
|
||||||
|
vOffsets[i] = desiredDiff.sub(currentDiff); // v_i - v_ref with v_ref = 0
|
||||||
|
}
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
return vOffsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------
|
/* --------------------
|
||||||
@@ -53,14 +116,13 @@ library LMSRStabilized {
|
|||||||
|
|
||||||
/// @notice Cost C(q) = b * (M + ln(Z))
|
/// @notice Cost C(q) = b * (M + ln(Z))
|
||||||
function cost(State storage s) internal view returns (int128) {
|
function cost(State storage s) internal view returns (int128) {
|
||||||
return cost(s.kappa, s.qInternal);
|
int128 b = _computeB(s);
|
||||||
|
return cost(b, s.qInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Cost C(q) = b * (M + ln(Z))
|
/// @notice Pure version: Cost C(q) = b * (M + ln(Z))
|
||||||
function cost(int128 kappa, int128[] memory qInternal) internal pure returns (int128) {
|
function cost(int128 b, int128[] memory qInternal) internal pure returns (int128) {
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
|
||||||
int128 b = kappa.mul(sizeMetric);
|
|
||||||
(int128 M, int128 Z) = _computeMAndZ(b, qInternal);
|
(int128 M, int128 Z) = _computeMAndZ(b, qInternal);
|
||||||
int128 lnZ = _ln(Z);
|
int128 lnZ = _ln(Z);
|
||||||
int128 inner = M.add(lnZ);
|
int128 inner = M.add(lnZ);
|
||||||
@@ -98,7 +160,8 @@ library LMSRStabilized {
|
|||||||
int128 a,
|
int128 a,
|
||||||
int128 limitPrice
|
int128 limitPrice
|
||||||
) internal view returns (int128 amountIn, int128 amountOut) {
|
) internal view returns (int128 amountIn, int128 amountOut) {
|
||||||
return swapAmountsForExactInput(s.nAssets, s.kappa, s.qInternal, i, j, a, limitPrice);
|
int128 b = _computeB(s);
|
||||||
|
return swapAmountsForExactInput(s.nAssets, b, s.qInternal, i, j, a, limitPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Closed-form asset-i -> asset-j amountOut in 64.64 fixed-point format (fee-free kernel)
|
/// @notice Pure version: Closed-form asset-i -> asset-j amountOut in 64.64 fixed-point format (fee-free kernel)
|
||||||
@@ -114,7 +177,7 @@ library LMSRStabilized {
|
|||||||
/// NOTE: Kernel is fee-free; fees should be handled by the wrapper/token layer.
|
/// NOTE: Kernel is fee-free; fees should be handled by the wrapper/token layer.
|
||||||
///
|
///
|
||||||
/// @param nAssets Number of assets in the pool
|
/// @param nAssets Number of assets in the pool
|
||||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
/// @param b Liquidity parameter κ (64.64 fixed point)
|
||||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||||
/// @param i Index of input asset
|
/// @param i Index of input asset
|
||||||
/// @param j Index of output asset
|
/// @param j Index of output asset
|
||||||
@@ -124,7 +187,7 @@ library LMSRStabilized {
|
|||||||
/// @return amountOut Amount of output asset j in 64.64 fixed-point format
|
/// @return amountOut Amount of output asset j in 64.64 fixed-point format
|
||||||
function swapAmountsForExactInput(
|
function swapAmountsForExactInput(
|
||||||
uint256 nAssets,
|
uint256 nAssets,
|
||||||
int128 kappa,
|
int128 b,
|
||||||
int128[] memory qInternal,
|
int128[] memory qInternal,
|
||||||
uint256 i,
|
uint256 i,
|
||||||
uint256 j,
|
uint256 j,
|
||||||
@@ -136,58 +199,33 @@ library LMSRStabilized {
|
|||||||
// Initialize amountIn to full amount (will be adjusted if limit price is hit)
|
// Initialize amountIn to full amount (will be adjusted if limit price is hit)
|
||||||
amountIn = a;
|
amountIn = a;
|
||||||
|
|
||||||
// Compute b and ensure positivity before deriving invB
|
// Ensure b > 0 and derive invB
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
|
||||||
int128 b = kappa.mul(sizeMetric);
|
|
||||||
require(b > int128(0), "LMSR: b<=0");
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
|
|
||||||
// Precompute reciprocal of b to avoid repeated divisions
|
|
||||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||||
|
|
||||||
// Guard: output asset must have non-zero effective weight to avoid degenerate/div-by-zero-like conditions
|
// Compute r0 = exp((q_i - q_j) / b) directly using invB (always > 0)
|
||||||
require(qInternal[j] > int128(0), "LMSR: e_j==0");
|
|
||||||
|
|
||||||
// Compute r0 = exp((q_i - q_j) / b) directly using invB
|
|
||||||
int128 r0 = _exp(qInternal[i].sub(qInternal[j]).mul(invB));
|
int128 r0 = _exp(qInternal[i].sub(qInternal[j]).mul(invB));
|
||||||
require(r0 > int128(0), "LMSR: r0<=0"); // equivalent to e_j > 0 check
|
|
||||||
|
|
||||||
// If a positive limitPrice is given, determine whether the full `a` would
|
// If a positive limitPrice is given, determine whether the full `a` would
|
||||||
// push the marginal price p_i/p_j beyond the limit; if so, truncate `a`.
|
// push the marginal price p_i/p_j beyond the limit; if so, truncate `a`.
|
||||||
// Marginal price ratio evolves as r(t) = r0 * exp(t/b) (since e_i multiplies by exp(t/b))
|
// Marginal price ratio evolves as r(t) = r0 * exp(t/b)
|
||||||
if (limitPrice > int128(0)) {
|
if (limitPrice > int128(0)) {
|
||||||
// r0 must be positive; if r0 == 0 then no risk of exceeding limit by increasing r.
|
|
||||||
require(r0 >= int128(0), "LMSR: r0<0");
|
|
||||||
if (r0 == int128(0)) {
|
|
||||||
// console2.log("r0 == 0 (input asset has zero weight), no limit truncation needed");
|
|
||||||
} else {
|
|
||||||
// If limitPrice <= current price, we revert (caller must choose a limit > current price to allow any fill)
|
|
||||||
if (limitPrice <= r0) {
|
if (limitPrice <= r0) {
|
||||||
revert("LMSR: limitPrice <= current price");
|
revert("LMSR: limitPrice <= current price");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute a_limit directly from ln(limit / r0): a_limit = b * ln(limit / r0)
|
|
||||||
int128 ratioLimitOverR0 = limitPrice.div(r0);
|
int128 ratioLimitOverR0 = limitPrice.div(r0);
|
||||||
require(ratioLimitOverR0 > int128(0), "LMSR: ratio<=0");
|
require(ratioLimitOverR0 > int128(0), "LMSR: ratio<=0");
|
||||||
|
|
||||||
int128 aLimitOverB = _ln(ratioLimitOverR0); // > 0
|
int128 aLimitOverB = _ln(ratioLimitOverR0); // > 0
|
||||||
|
|
||||||
// aLimit = b * aLimitOverB
|
|
||||||
int128 aLimit64 = b.mul(aLimitOverB);
|
int128 aLimit64 = b.mul(aLimitOverB);
|
||||||
|
|
||||||
// If computed aLimit is less than the requested a, use the truncated value.
|
|
||||||
if (aLimit64 < a) {
|
if (aLimit64 < a) {
|
||||||
amountIn = aLimit64; // Store the truncated input amount
|
amountIn = aLimit64;
|
||||||
a = aLimit64; // Use truncated amount for calculations
|
a = aLimit64;
|
||||||
} else {
|
|
||||||
// console2.log("Not truncating: aLimit64 >= a");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute a/b safely and guard against very large arguments to exp()
|
// compute a/b safely and guard against very large arguments to exp()
|
||||||
int128 aOverB = a.mul(invB);
|
int128 aOverB = a.mul(invB);
|
||||||
// Protect exp from enormous inputs (consistent with recenter thresholds)
|
|
||||||
require(aOverB <= EXP_LIMIT, "LMSR: a/b too large (would overflow exp)");
|
require(aOverB <= EXP_LIMIT, "LMSR: a/b too large (would overflow exp)");
|
||||||
|
|
||||||
// Use the closed-form fee-free formula:
|
// Use the closed-form fee-free formula:
|
||||||
@@ -196,17 +234,14 @@ library LMSRStabilized {
|
|||||||
int128 oneMinusExpNeg = ONE.sub(expNeg);
|
int128 oneMinusExpNeg = ONE.sub(expNeg);
|
||||||
int128 inner = ONE.add(r0.mul(oneMinusExpNeg));
|
int128 inner = ONE.add(r0.mul(oneMinusExpNeg));
|
||||||
|
|
||||||
// If inner <= 0 then cap output to the current balance q_j (cannot withdraw more than q_j)
|
// If inner <= 0 then return zero (numeric guard; reserve caps are enforced by wrapper)
|
||||||
if (inner <= int128(0)) {
|
if (inner <= int128(0)) {
|
||||||
int128 qj64 = qInternal[j];
|
return (amountIn, int128(0));
|
||||||
return (amountIn, qj64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int128 lnInner = _ln(inner);
|
int128 lnInner = _ln(inner);
|
||||||
int128 b_lnInner = b.mul(lnInner);
|
amountOut = b.mul(lnInner);
|
||||||
amountOut = b_lnInner;
|
|
||||||
|
|
||||||
// Safety check
|
|
||||||
if (amountOut <= 0) {
|
if (amountOut <= 0) {
|
||||||
return (0, 0);
|
return (0, 0);
|
||||||
}
|
}
|
||||||
@@ -230,7 +265,8 @@ library LMSRStabilized {
|
|||||||
uint256 j,
|
uint256 j,
|
||||||
int128 limitPrice
|
int128 limitPrice
|
||||||
) internal view returns (int128 amountIn, int128 amountOut) {
|
) internal view returns (int128 amountIn, int128 amountOut) {
|
||||||
return swapAmountsForPriceLimit(s.nAssets, s.kappa, s.qInternal, i, j, limitPrice);
|
int128 b = _computeB(s);
|
||||||
|
return swapAmountsForPriceLimit(s.nAssets, b, s.qInternal, i, j, limitPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Maximum input/output pair possible when swapping from asset i to asset j
|
/// @notice Pure version: Maximum input/output pair possible when swapping from asset i to asset j
|
||||||
@@ -240,7 +276,7 @@ library LMSRStabilized {
|
|||||||
/// j-balance, amountOut is capped and amountIn is solved for the capped output.
|
/// j-balance, amountOut is capped and amountIn is solved for the capped output.
|
||||||
///
|
///
|
||||||
/// @param nAssets Number of assets in the pool
|
/// @param nAssets Number of assets in the pool
|
||||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
/// @param b Liquidity parameter κ (64.64 fixed point)
|
||||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||||
/// @param i Index of input asset
|
/// @param i Index of input asset
|
||||||
/// @param j Index of output asset
|
/// @param j Index of output asset
|
||||||
@@ -249,7 +285,7 @@ library LMSRStabilized {
|
|||||||
/// @return amountOut Corresponding maximum output amount in 64.64 fixed-point format
|
/// @return amountOut Corresponding maximum output amount in 64.64 fixed-point format
|
||||||
function swapAmountsForPriceLimit(
|
function swapAmountsForPriceLimit(
|
||||||
uint256 nAssets,
|
uint256 nAssets,
|
||||||
int128 kappa,
|
int128 b,
|
||||||
int128[] memory qInternal,
|
int128[] memory qInternal,
|
||||||
uint256 i,
|
uint256 i,
|
||||||
uint256 j,
|
uint256 j,
|
||||||
@@ -258,74 +294,31 @@ library LMSRStabilized {
|
|||||||
require(i < nAssets && j < nAssets, "LMSR: idx");
|
require(i < nAssets && j < nAssets, "LMSR: idx");
|
||||||
require(limitPrice > int128(0), "LMSR: limitPrice <= 0");
|
require(limitPrice > int128(0), "LMSR: limitPrice <= 0");
|
||||||
|
|
||||||
// Compute b and ensure positivity before deriving invB
|
// Ensure positivity and derive invB
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
|
||||||
int128 b = kappa.mul(sizeMetric);
|
|
||||||
require(b > int128(0), "LMSR: b<=0");
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
|
|
||||||
// Precompute reciprocal of b to avoid repeated divisions
|
|
||||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||||
|
|
||||||
// Guard: output asset must have non-zero effective weight to avoid degenerate/div-by-zero-like conditions
|
// Compute r0 = exp((q_i - q_j) / b)
|
||||||
require(qInternal[j] > int128(0), "LMSR: e_j==0");
|
|
||||||
|
|
||||||
// Compute r0 = exp((q_i - q_j) / b) directly using invB
|
|
||||||
int128 r0 = _exp(qInternal[i].sub(qInternal[j]).mul(invB));
|
int128 r0 = _exp(qInternal[i].sub(qInternal[j]).mul(invB));
|
||||||
|
|
||||||
// Mirror swapAmountsForExactInput behavior: treat invalid r0 as an error condition.
|
// If current price already exceeds or equals limit, revert (no room to move)
|
||||||
// Revert if r0 is non-positive (no finite trade under a price limit).
|
|
||||||
require(r0 > int128(0), "LMSR: r0<=0");
|
|
||||||
|
|
||||||
// If current price already exceeds or equals limit, revert the same way swapAmountsForExactInput does.
|
|
||||||
if (r0 >= limitPrice) {
|
if (r0 >= limitPrice) {
|
||||||
revert("LMSR: limitPrice <= current price");
|
revert("LMSR: limitPrice <= current price");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the price change factor: limitPrice/r0
|
|
||||||
int128 priceChangeFactor = limitPrice.div(r0);
|
|
||||||
|
|
||||||
// ln(priceChangeFactor) gives us the maximum allowed delta in the exponent
|
|
||||||
int128 maxDeltaExponent = _ln(priceChangeFactor);
|
|
||||||
|
|
||||||
// Maximum input capable of reaching the price limit:
|
|
||||||
// x_max = b * ln(limitPrice / r0)
|
// x_max = b * ln(limitPrice / r0)
|
||||||
|
int128 priceChangeFactor = limitPrice.div(r0);
|
||||||
|
int128 maxDeltaExponent = _ln(priceChangeFactor);
|
||||||
int128 amountInMax = b.mul(maxDeltaExponent);
|
int128 amountInMax = b.mul(maxDeltaExponent);
|
||||||
|
|
||||||
// The maximum output y corresponding to that input:
|
// y_max = b * ln(1 + r0 * (1 - exp(-x_max/b)))
|
||||||
// y = b * ln(1 + (e_i/e_j) * (1 - exp(-x_max/b)))
|
|
||||||
int128 expTerm = ONE.sub(_exp(maxDeltaExponent.neg()));
|
int128 expTerm = ONE.sub(_exp(maxDeltaExponent.neg()));
|
||||||
int128 innerTerm = r0.mul(expTerm);
|
int128 innerTerm = r0.mul(expTerm);
|
||||||
int128 lnTerm = _ln(ONE.add(innerTerm));
|
int128 lnTerm = _ln(ONE.add(innerTerm));
|
||||||
int128 maxOutput = b.mul(lnTerm);
|
int128 maxOutput = b.mul(lnTerm);
|
||||||
|
|
||||||
// Current balance of asset j (in 64.64)
|
|
||||||
int128 qj64 = qInternal[j];
|
|
||||||
|
|
||||||
// Initialize outputs to the computed maxima
|
|
||||||
amountIn = amountInMax;
|
amountIn = amountInMax;
|
||||||
amountOut = maxOutput;
|
amountOut = maxOutput;
|
||||||
|
|
||||||
// If the calculated maximum output exceeds the balance, cap output and solve for input.
|
|
||||||
if (maxOutput > qj64) {
|
|
||||||
amountOut = qj64;
|
|
||||||
|
|
||||||
// Solve inverse relation for input given capped output:
|
|
||||||
// Given y = amountOut, let E = exp(y/b). Then
|
|
||||||
// 1 - exp(-a/b) = (E - 1) / r0
|
|
||||||
// exp(-a/b) = 1 - (E - 1) / r0 = (r0 + 1 - E) / r0
|
|
||||||
// a = -b * ln( (r0 + 1 - E) / r0 ) = b * ln( r0 / (r0 + 1 - E) )
|
|
||||||
int128 E = _exp(amountOut.mul(invB)); // exp(y/b)
|
|
||||||
int128 rhs = r0.add(ONE).sub(E); // r0 + 1 - E
|
|
||||||
|
|
||||||
// If rhs <= 0 due to numerical issues, fall back to amountInMax
|
|
||||||
if (rhs <= int128(0)) {
|
|
||||||
amountIn = amountInMax;
|
|
||||||
} else {
|
|
||||||
amountIn = b.mul(_ln(r0.div(rhs)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (amountIn, amountOut);
|
return (amountIn, amountOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +333,8 @@ library LMSRStabilized {
|
|||||||
uint256 i,
|
uint256 i,
|
||||||
int128 a
|
int128 a
|
||||||
) internal view returns (int128 amountIn, int128 amountOut) {
|
) internal view returns (int128 amountIn, int128 amountOut) {
|
||||||
return swapAmountsForMint(s.nAssets, s.kappa, s.qInternal, i, a);
|
int128 b = _computeB(s);
|
||||||
|
return swapAmountsForMint(s.nAssets, b, s.qInternal, i, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Compute LP-size increase when minting from a single-token input using bisection only.
|
/// @notice Pure version: Compute LP-size increase when minting from a single-token input using bisection only.
|
||||||
@@ -350,7 +344,7 @@ library LMSRStabilized {
|
|||||||
/// x_j = b * ln( r0_j / (r0_j + 1 - exp(y_j / b)) ), r0_j = exp((q_i - q_j)/b).
|
/// x_j = b * ln( r0_j / (r0_j + 1 - exp(y_j / b)) ), r0_j = exp((q_i - q_j)/b).
|
||||||
/// Bisection is used (no Newton) to keep implementation compact and gas-friendly.
|
/// Bisection is used (no Newton) to keep implementation compact and gas-friendly.
|
||||||
/// @param nAssets Number of assets in the pool
|
/// @param nAssets Number of assets in the pool
|
||||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
/// @param b Liquidity parameter κ (64.64 fixed point)
|
||||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||||
/// @param i Index of input asset
|
/// @param i Index of input asset
|
||||||
/// @param a Amount of input asset (in int128 format, 64.64 fixed-point)
|
/// @param a Amount of input asset (in int128 format, 64.64 fixed-point)
|
||||||
@@ -358,7 +352,7 @@ library LMSRStabilized {
|
|||||||
/// @return amountOut LP size-metric increase (alpha * S)
|
/// @return amountOut LP size-metric increase (alpha * S)
|
||||||
function swapAmountsForMint(
|
function swapAmountsForMint(
|
||||||
uint256 nAssets,
|
uint256 nAssets,
|
||||||
int128 kappa,
|
int128 b,
|
||||||
int128[] memory qInternal,
|
int128[] memory qInternal,
|
||||||
uint256 i,
|
uint256 i,
|
||||||
int128 a
|
int128 a
|
||||||
@@ -366,12 +360,11 @@ library LMSRStabilized {
|
|||||||
require(i < nAssets, "LMSR: idx");
|
require(i < nAssets, "LMSR: idx");
|
||||||
require(a > int128(0), "LMSR: amount <= 0");
|
require(a > int128(0), "LMSR: amount <= 0");
|
||||||
|
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
// Compute size metric S for output computation; use provided b for curvature
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
int128 S = _computeSizeMetric(qInternal);
|
||||||
int128 b = kappa.mul(sizeMetric);
|
require(S > int128(0), "LMSR: size metric zero");
|
||||||
require(b > int128(0), "LMSR: b<=0");
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||||
int128 S = sizeMetric;
|
|
||||||
|
|
||||||
uint256 n = nAssets;
|
uint256 n = nAssets;
|
||||||
|
|
||||||
@@ -552,7 +545,8 @@ library LMSRStabilized {
|
|||||||
uint256 i,
|
uint256 i,
|
||||||
int128 alpha
|
int128 alpha
|
||||||
) internal view returns (int128 amountOut, int128 amountIn) {
|
) internal view returns (int128 amountOut, int128 amountIn) {
|
||||||
return swapAmountsForBurn(s.nAssets, s.kappa, s.qInternal, i, alpha);
|
int128 b = _computeB(s);
|
||||||
|
return swapAmountsForBurn(s.nAssets, b, s.qInternal, i, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Compute single-asset payout when burning a proportional share alpha of the pool.
|
/// @notice Pure version: Compute single-asset payout when burning a proportional share alpha of the pool.
|
||||||
@@ -565,7 +559,7 @@ library LMSRStabilized {
|
|||||||
/// Treat any per-asset rhs<=0 as "this asset contributes zero" (do not revert).
|
/// Treat any per-asset rhs<=0 as "this asset contributes zero" (do not revert).
|
||||||
/// Revert only if the final single-asset payout is zero.
|
/// Revert only if the final single-asset payout is zero.
|
||||||
/// @param nAssets Number of assets in the pool
|
/// @param nAssets Number of assets in the pool
|
||||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
/// @param b Liquidity parameter κ (64.64 fixed point)
|
||||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||||
/// @param i Index of output asset
|
/// @param i Index of output asset
|
||||||
/// @param alpha Proportional share to burn (0 < alpha <= 1)
|
/// @param alpha Proportional share to burn (0 < alpha <= 1)
|
||||||
@@ -573,7 +567,7 @@ library LMSRStabilized {
|
|||||||
/// @return amountIn LP size-metric redeemed (alpha * S)
|
/// @return amountIn LP size-metric redeemed (alpha * S)
|
||||||
function swapAmountsForBurn(
|
function swapAmountsForBurn(
|
||||||
uint256 nAssets,
|
uint256 nAssets,
|
||||||
int128 kappa,
|
int128 b,
|
||||||
int128[] memory qInternal,
|
int128[] memory qInternal,
|
||||||
uint256 i,
|
uint256 i,
|
||||||
int128 alpha
|
int128 alpha
|
||||||
@@ -581,9 +575,9 @@ library LMSRStabilized {
|
|||||||
require(i < nAssets, "LMSR: idx");
|
require(i < nAssets, "LMSR: idx");
|
||||||
require(alpha > int128(0) && alpha <= ONE, "LMSR: alpha");
|
require(alpha > int128(0) && alpha <= ONE, "LMSR: alpha");
|
||||||
|
|
||||||
|
// Compute size metric S for payout normalization; use provided b for curvature
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||||
int128 b = kappa.mul(sizeMetric);
|
|
||||||
require(b > int128(0), "LMSR: b<=0");
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||||
|
|
||||||
@@ -726,20 +720,47 @@ library LMSRStabilized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @notice Optional helper: recompute qInternal from reserves and explicit virtual offsets.
|
||||||
|
/// @dev This variant is useful if callers manage virtual offsets off-chain and want to preserve price neutrality
|
||||||
|
/// across proportional mints/burns by applying updated offsets alongside reserve changes.
|
||||||
|
/// @param newReservesInternal New reserves vector in 64.64 (converted from uint balances)
|
||||||
|
/// @param vOffsets Per-asset virtual offsets to apply (same length as nAssets)
|
||||||
|
function updateForProportionalChangeWithOffsets(
|
||||||
|
State storage s,
|
||||||
|
int128[] memory newReservesInternal,
|
||||||
|
int128[] memory vOffsets
|
||||||
|
) internal {
|
||||||
|
require(newReservesInternal.length == s.nAssets, "LMSR: length mismatch");
|
||||||
|
require(vOffsets.length == s.nAssets, "LMSR: v length mismatch");
|
||||||
|
|
||||||
|
int128[] memory combined = new int128[](s.nAssets);
|
||||||
|
for (uint i = 0; i < s.nAssets; ) {
|
||||||
|
combined[i] = newReservesInternal[i].add(vOffsets[i]);
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
int128 newTotal = _computeSizeMetric(combined);
|
||||||
|
require(newTotal > int128(0), "LMSR: new total zero");
|
||||||
|
|
||||||
|
for (uint i = 0; i < s.nAssets; ) {
|
||||||
|
s.qInternal[i] = combined[i];
|
||||||
|
unchecked { i++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @notice Price-share of asset i: exp(z_i) / Z (64.64)
|
/// @notice Price-share of asset i: exp(z_i) / Z (64.64)
|
||||||
function priceShare(State storage s, uint256 i) internal view returns (int128) {
|
function priceShare(State storage s, uint256 i) internal view returns (int128) {
|
||||||
return priceShare(s.kappa, s.qInternal, i);
|
int128 b = _computeB(s);
|
||||||
|
return priceShare(b, s.qInternal, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Price-share of asset i: exp(z_i) / Z (64.64)
|
/// @notice Pure version: Price-share of asset i: exp(z_i) / Z (64.64)
|
||||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
/// @param b Liquidity parameter κ (64.64 fixed point)
|
||||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||||
/// @param i Index of asset
|
/// @param i Index of asset
|
||||||
/// @return Price share in 64.64 fixed-point format
|
/// @return Price share in 64.64 fixed-point format
|
||||||
function priceShare(int128 kappa, int128[] memory qInternal, uint256 i) internal pure returns (int128) {
|
function priceShare(int128 b, int128[] memory qInternal, uint256 i) internal pure returns (int128) {
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
|
||||||
int128 b = kappa.mul(sizeMetric);
|
|
||||||
uint len = qInternal.length;
|
uint len = qInternal.length;
|
||||||
require(len > 0, "LMSR: no assets");
|
require(len > 0, "LMSR: no assets");
|
||||||
|
|
||||||
@@ -797,22 +818,20 @@ library LMSRStabilized {
|
|||||||
/// @notice Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64
|
/// @notice Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64
|
||||||
/// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0.
|
/// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0.
|
||||||
function price(State storage s, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal view returns (int128) {
|
function price(State storage s, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal view returns (int128) {
|
||||||
return price(s.nAssets, s.kappa, s.qInternal, baseTokenIndex, quoteTokenIndex);
|
int128 b = _computeB(s);
|
||||||
|
return price(s.nAssets, b, s.qInternal, baseTokenIndex, quoteTokenIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64
|
/// @notice Pure version: Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64
|
||||||
/// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0.
|
/// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0.
|
||||||
/// @param nAssets Number of assets in the pool
|
/// @param nAssets Number of assets in the pool
|
||||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
/// @param b Liquidity parameter κ (64.64 fixed point)
|
||||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||||
/// @param baseTokenIndex Index of base token
|
/// @param baseTokenIndex Index of base token
|
||||||
/// @param quoteTokenIndex Index of quote token
|
/// @param quoteTokenIndex Index of quote token
|
||||||
/// @return Price in 64.64 fixed-point format
|
/// @return Price in 64.64 fixed-point format
|
||||||
function price(uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal pure returns (int128) {
|
function price(uint256 nAssets, int128 b, int128[] memory qInternal, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal pure returns (int128) {
|
||||||
require(baseTokenIndex < nAssets && quoteTokenIndex < nAssets, "LMSR: idx");
|
require(baseTokenIndex < nAssets && quoteTokenIndex < nAssets, "LMSR: idx");
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
|
||||||
int128 b = kappa.mul(sizeMetric);
|
|
||||||
require(b > int128(0), "LMSR: b<=0");
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
|
|
||||||
// Use reciprocal of b to avoid repeated divisions
|
// Use reciprocal of b to avoid repeated divisions
|
||||||
@@ -825,26 +844,22 @@ library LMSRStabilized {
|
|||||||
/// @notice Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64)
|
/// @notice Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64)
|
||||||
/// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b)
|
/// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b)
|
||||||
function poolPrice(State storage s, uint256 quoteTokenIndex) internal view returns (int128) {
|
function poolPrice(State storage s, uint256 quoteTokenIndex) internal view returns (int128) {
|
||||||
return poolPrice(s.nAssets, s.kappa, s.qInternal, quoteTokenIndex);
|
int128 b = _computeB(s);
|
||||||
|
return poolPrice(s.nAssets, b, s.qInternal, quoteTokenIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Pure version: Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64)
|
/// @notice Pure version: Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64)
|
||||||
/// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b)
|
/// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b)
|
||||||
/// @param nAssets Number of assets in the pool
|
/// @param nAssets Number of assets in the pool
|
||||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
/// @param b Liquidity parameter κ (64.64 fixed point)
|
||||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||||
/// @param quoteTokenIndex Index of quote token
|
/// @param quoteTokenIndex Index of quote token
|
||||||
/// @return Pool price in 64.64 fixed-point format
|
/// @return Pool price in 64.64 fixed-point format
|
||||||
function poolPrice(uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) {
|
function poolPrice(uint256 nAssets, int128 b, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) {
|
||||||
require(quoteTokenIndex < nAssets, "LMSR: idx");
|
require(quoteTokenIndex < nAssets, "LMSR: idx");
|
||||||
// Compute b and ensure positivity
|
// Ensure positivity and compute total size metric S = sum q_i
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
|
||||||
int128 b = kappa.mul(sizeMetric);
|
|
||||||
require(b > int128(0), "LMSR: b<=0");
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
|
int128 S = _computeSizeMetric(qInternal);
|
||||||
// Compute total size metric S = sum q_i
|
|
||||||
int128 S = sizeMetric;
|
|
||||||
require(S > int128(0), "LMSR: size zero");
|
require(S > int128(0), "LMSR: size zero");
|
||||||
|
|
||||||
// Precompute reciprocal of b
|
// Precompute reciprocal of b
|
||||||
@@ -870,9 +885,8 @@ library LMSRStabilized {
|
|||||||
Slippage -> b computation & resize-triggered rescale
|
Slippage -> b computation & resize-triggered rescale
|
||||||
-------------------- */
|
-------------------- */
|
||||||
|
|
||||||
/// @notice Internal helper to compute kappa from slippage parameters.
|
/// @notice Internal helper to compute kappa from slippage parameters. (Deprecated in favor of computeBFromSlippage)
|
||||||
/// @dev Returns κ in Q64.64. Implemented as internal so callers within the library can use it
|
/// @dev Returns κ in Q64.64 for reference. Prefer computing b directly via computeBFromSlippage.
|
||||||
/// without resorting to external calls.
|
|
||||||
function computeKappaFromSlippage(
|
function computeKappaFromSlippage(
|
||||||
uint256 nAssets,
|
uint256 nAssets,
|
||||||
int128 tradeFrac,
|
int128 tradeFrac,
|
||||||
@@ -920,7 +934,57 @@ library LMSRStabilized {
|
|||||||
return kappa;
|
return kappa;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Legacy-compatible init: compute kappa from slippage parameters and delegate to kappa-based init.
|
/// @notice Compute fixed b from a target slippage profile.
|
||||||
|
/// @dev For a trade of fraction f of the size metric S, targeting slippage s, we have y := -ln(E)/f with
|
||||||
|
/// E = (1 - s*(n-1)) / (1 + s) and b = S / y.
|
||||||
|
function computeBFromSlippage(
|
||||||
|
uint256 nAssets,
|
||||||
|
int128 sizeMetricS,
|
||||||
|
int128 tradeFrac,
|
||||||
|
int128 targetSlippage
|
||||||
|
) internal pure returns (int128) {
|
||||||
|
require(nAssets > 1, "LMSR: n>1 required");
|
||||||
|
require(sizeMetricS > int128(0), "LMSR: S<=0");
|
||||||
|
|
||||||
|
// f must be in (0,1)
|
||||||
|
int128 f = tradeFrac;
|
||||||
|
require(f > int128(0), "LMSR: f=0");
|
||||||
|
require(f < ONE, "LMSR: f>=1");
|
||||||
|
|
||||||
|
int128 onePlusS = ONE.add(targetSlippage);
|
||||||
|
|
||||||
|
int128 n64 = ABDKMath64x64.fromUInt(nAssets);
|
||||||
|
int128 nMinus1_64 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||||
|
|
||||||
|
// If 1 + s >= n then equal-inventories closed-form applies
|
||||||
|
bool useEqual = (onePlusS >= n64);
|
||||||
|
|
||||||
|
// E candidate used in deriving y = -ln(E)/f (same expression in both branches)
|
||||||
|
int128 numerator = ONE.sub(targetSlippage.mul(nMinus1_64)); // 1 - s*(n-1)
|
||||||
|
int128 denominator = onePlusS; // 1 + s
|
||||||
|
|
||||||
|
if (useEqual) {
|
||||||
|
// Guard numerator to ensure E in (0,1)
|
||||||
|
require(numerator > int128(0), "LMSR: s too large for n");
|
||||||
|
} else {
|
||||||
|
require(numerator > int128(0), "LMSR: bad slippage or n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int128 E_candidate = numerator.div(denominator);
|
||||||
|
require(E_candidate > int128(0) && E_candidate < ONE, "LMSR: bad E ratio");
|
||||||
|
|
||||||
|
// y = -ln(E) / f
|
||||||
|
int128 lnE = _ln(E_candidate);
|
||||||
|
int128 y = lnE.neg().div(f);
|
||||||
|
require(y > int128(0), "LMSR: y<=0");
|
||||||
|
|
||||||
|
// b = S / y
|
||||||
|
int128 b = ABDKMath64x64.div(sizeMetricS, y);
|
||||||
|
require(b > int128(0), "LMSR: b<=0");
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Legacy-compatible init: compute fixed b from slippage parameters and delegate to fixed-b init.
|
||||||
/// @dev Provides backward compatibility for callers that still use the (q, tradeFrac, targetSlippage) init signature.
|
/// @dev Provides backward compatibility for callers that still use the (q, tradeFrac, targetSlippage) init signature.
|
||||||
function init(
|
function init(
|
||||||
State storage s,
|
State storage s,
|
||||||
@@ -928,10 +992,11 @@ library LMSRStabilized {
|
|||||||
int128 tradeFrac,
|
int128 tradeFrac,
|
||||||
int128 targetSlippage
|
int128 targetSlippage
|
||||||
) internal {
|
) internal {
|
||||||
// compute kappa using the internal helper
|
// compute b directly from the current size metric and the slippage profile
|
||||||
int128 kappa = computeKappaFromSlippage(initialQInternal.length, tradeFrac, targetSlippage);
|
int128 S = _computeSizeMetric(initialQInternal);
|
||||||
// forward to the new kappa-based init
|
int128 b = computeBFromSlippage(initialQInternal.length, S, tradeFrac, targetSlippage);
|
||||||
init(s, initialQInternal, kappa);
|
// forward to the fixed-b init
|
||||||
|
init(s, initialQInternal, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -940,12 +1005,12 @@ library LMSRStabilized {
|
|||||||
function deinit(State storage s) internal {
|
function deinit(State storage s) internal {
|
||||||
// Reset core state
|
// Reset core state
|
||||||
s.nAssets = 0;
|
s.nAssets = 0;
|
||||||
s.kappa = int128(0);
|
s.bFixed = int128(0);
|
||||||
|
|
||||||
// Clear qInternal array
|
// Clear qInternal array
|
||||||
delete s.qInternal;
|
delete s.qInternal;
|
||||||
|
|
||||||
// Note: init(...) will recompute kappa and nAssets on first mint.
|
// Note: initWithFixedB(...) will set b and nAssets on first mint.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Compute M (shift) and Z (sum of exponentials) dynamically
|
/// @notice Compute M (shift) and Z (sum of exponentials) dynamically
|
||||||
@@ -1023,9 +1088,8 @@ library LMSRStabilized {
|
|||||||
|
|
||||||
/// @notice Compute b from kappa and current asset quantities
|
/// @notice Compute b from kappa and current asset quantities
|
||||||
function _computeB(State storage s) internal view returns (int128) {
|
function _computeB(State storage s) internal view returns (int128) {
|
||||||
int128 sizeMetric = _computeSizeMetric(s.qInternal);
|
// require(s.bFixed > int128(0), "LMSR: b not set");
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
return s.bFixed;
|
||||||
return s.kappa.mul(sizeMetric);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,11 +48,9 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
|||||||
|
|
||||||
function wrapperToken() external view returns (NativeWrapper) { return WRAPPER_TOKEN; }
|
function wrapperToken() external view returns (NativeWrapper) { return WRAPPER_TOKEN; }
|
||||||
|
|
||||||
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
/// @notice Fixed LMSR curvature parameter b (Q64.64)
|
||||||
/// @dev Pool is constructed with a fixed κ. Clients that previously passed tradeFrac/targetSlippage
|
int128 private immutable B_FIXED;
|
||||||
/// should use LMSRStabilized.computeKappaFromSlippage(...) to derive κ and pass it here.
|
function bFixed() external view returns (int128) { return B_FIXED; }
|
||||||
int128 private immutable KAPPA; // kappa in Q64.64
|
|
||||||
function kappa() external view returns (int128) { return KAPPA; }
|
|
||||||
|
|
||||||
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
||||||
uint256 private immutable SWAP_FEE_PPM;
|
uint256 private immutable SWAP_FEE_PPM;
|
||||||
@@ -100,7 +98,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
|||||||
/// @param symbol_ LP token symbol
|
/// @param symbol_ LP token symbol
|
||||||
/// @param tokens_ token addresses (n)
|
/// @param tokens_ token addresses (n)
|
||||||
/// @param bases_ scaling _bases for each token (n) - used when converting to/from internal 64.64 amounts
|
/// @param bases_ scaling _bases for each token (n) - used when converting to/from internal 64.64 amounts
|
||||||
/// @param kappa_ liquidity parameter κ (Q64.64) used to derive b = κ * S(q)
|
/// @param bFixed_ fixed LMSR curvature b (Q64.64)
|
||||||
/// @param swapFeePpm_ fee in parts-per-million, taken from swap input amounts before LMSR calculations
|
/// @param swapFeePpm_ fee in parts-per-million, taken from swap input amounts before LMSR calculations
|
||||||
/// @param flashFeePpm_ fee in parts-per-million, taken for flash loans
|
/// @param flashFeePpm_ fee in parts-per-million, taken for flash loans
|
||||||
/// @param swapImpl_ address of the SwapMint implementation contract
|
/// @param swapImpl_ address of the SwapMint implementation contract
|
||||||
@@ -111,7 +109,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
|||||||
string memory symbol_,
|
string memory symbol_,
|
||||||
IERC20[] memory tokens_,
|
IERC20[] memory tokens_,
|
||||||
uint256[] memory bases_,
|
uint256[] memory bases_,
|
||||||
int128 kappa_,
|
int128 bFixed_,
|
||||||
uint256 swapFeePpm_,
|
uint256 swapFeePpm_,
|
||||||
uint256 flashFeePpm_,
|
uint256 flashFeePpm_,
|
||||||
uint256 protocolFeePpm_,
|
uint256 protocolFeePpm_,
|
||||||
@@ -129,7 +127,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
|||||||
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
|
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
|
||||||
_tokens = tokens_;
|
_tokens = tokens_;
|
||||||
_bases = bases_;
|
_bases = bases_;
|
||||||
KAPPA = kappa_;
|
B_FIXED = bFixed_;
|
||||||
require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm");
|
require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm");
|
||||||
SWAP_FEE_PPM = swapFeePpm_;
|
SWAP_FEE_PPM = swapFeePpm_;
|
||||||
require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm");
|
require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm");
|
||||||
@@ -184,7 +182,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
|||||||
PartyPoolMintImpl.initialMint.selector,
|
PartyPoolMintImpl.initialMint.selector,
|
||||||
receiver,
|
receiver,
|
||||||
lpTokens,
|
lpTokens,
|
||||||
KAPPA
|
B_FIXED
|
||||||
);
|
);
|
||||||
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
||||||
return abi.decode(result, (uint256));
|
return abi.decode(result, (uint256));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
// Initialization Mint
|
// Initialization Mint
|
||||||
//
|
//
|
||||||
|
|
||||||
function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external payable native killable nonReentrant
|
function initialMint(address receiver, uint256 lpTokens, int128 B_FIXED) external payable native killable nonReentrant
|
||||||
returns (uint256 lpMinted) {
|
returns (uint256 lpMinted) {
|
||||||
uint256 n = _tokens.length;
|
uint256 n = _tokens.length;
|
||||||
|
|
||||||
@@ -44,8 +44,8 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
unchecked { i++; }
|
unchecked { i++; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the stabilized LMSR state with provided kappa
|
// Initialize the stabilized LMSR state with provided fixed b
|
||||||
_lmsr.init(newQInternal, KAPPA);
|
_lmsr.init(newQInternal, B_FIXED);
|
||||||
|
|
||||||
// Compute actual LP _tokens to mint based on size metric (scaled)
|
// Compute actual LP _tokens to mint based on size metric (scaled)
|
||||||
if( lpTokens != 0 )
|
if( lpTokens != 0 )
|
||||||
@@ -288,7 +288,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
|
|
||||||
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
|
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
|
||||||
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) =
|
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) =
|
||||||
LMSRStabilized.swapAmountsForMint(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
LMSRStabilized.swapAmountsForMint(lmsrState.nAssets, lmsrState.bFixed, lmsrState.qInternal,
|
||||||
inputTokenIndex, netInternalGuess);
|
inputTokenIndex, netInternalGuess);
|
||||||
|
|
||||||
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
|
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
|
||||||
@@ -456,7 +456,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
|
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
|
||||||
|
|
||||||
// Use LMSR view to compute single-asset payout and burned size-metric
|
// Use LMSR view to compute single-asset payout and burned size-metric
|
||||||
(int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
(int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.bFixed, lmsrState.qInternal,
|
||||||
inputTokenIndex, alpha);
|
inputTokenIndex, alpha);
|
||||||
|
|
||||||
// Convert payoutInternal -> uint (floor) to favor pool
|
// Convert payoutInternal -> uint (floor) to favor pool
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
|||||||
uint256 outputTokenIndex,
|
uint256 outputTokenIndex,
|
||||||
int128 limitPrice,
|
int128 limitPrice,
|
||||||
uint256[] memory bases,
|
uint256[] memory bases,
|
||||||
int128 kappa,
|
int128 b,
|
||||||
int128[] memory qInternal,
|
int128[] memory qInternal,
|
||||||
uint256 swapFeePpm
|
uint256 swapFeePpm
|
||||||
) external pure returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
) external pure returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||||
// Compute internal maxima at the price limit
|
// Compute internal maxima at the price limit
|
||||||
(int128 amountInInternal, int128 amountOutInternal) = LMSRStabilized.swapAmountsForPriceLimit(
|
(int128 amountInInternal, int128 amountOutInternal) = LMSRStabilized.swapAmountsForPriceLimit(
|
||||||
bases.length, kappa, qInternal,
|
bases.length, b, qInternal,
|
||||||
inputTokenIndex, outputTokenIndex, limitPrice);
|
inputTokenIndex, outputTokenIndex, limitPrice);
|
||||||
|
|
||||||
// Convert input to uint (ceil) and output to uint (floor)
|
// Convert input to uint (ceil) and output to uint (floor)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
|
|||||||
LMSRStabilized.State memory lmsr = pool.LMSR();
|
LMSRStabilized.State memory lmsr = pool.LMSR();
|
||||||
require(baseTokenIndex < lmsr.nAssets && quoteTokenIndex < lmsr.nAssets, "price: idx");
|
require(baseTokenIndex < lmsr.nAssets && quoteTokenIndex < lmsr.nAssets, "price: idx");
|
||||||
require(lmsr.nAssets > 0, "price: uninit");
|
require(lmsr.nAssets > 0, "price: uninit");
|
||||||
return LMSRStabilized.price(lmsr.nAssets, pool.kappa(), lmsr.qInternal, baseTokenIndex, quoteTokenIndex);
|
return LMSRStabilized.price(lmsr.nAssets, pool.bFixed(), lmsr.qInternal, baseTokenIndex, quoteTokenIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Price of one LP token denominated in `quote` as Q64.64.
|
/// @notice Price of one LP token denominated in `quote` as Q64.64.
|
||||||
@@ -50,7 +50,7 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
|
|||||||
require(quoteTokenIndex < lmsr.nAssets, "poolPrice: idx");
|
require(quoteTokenIndex < lmsr.nAssets, "poolPrice: idx");
|
||||||
|
|
||||||
// price per unit of qTotal (Q64.64) from LMSR
|
// price per unit of qTotal (Q64.64) from LMSR
|
||||||
int128 pricePerQ = LMSRStabilized.poolPrice(lmsr.nAssets, pool.kappa(), lmsr.qInternal, quoteTokenIndex);
|
int128 pricePerQ = LMSRStabilized.poolPrice(lmsr.nAssets, pool.bFixed(), lmsr.qInternal, quoteTokenIndex);
|
||||||
|
|
||||||
// total internal q (qTotal) as Q64.64
|
// total internal q (qTotal) as Q64.64
|
||||||
int128 qTotal = LMSRStabilized._computeSizeMetric(lmsr.qInternal);
|
int128 qTotal = LMSRStabilized._computeSizeMetric(lmsr.qInternal);
|
||||||
@@ -105,7 +105,7 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
|
|||||||
|
|
||||||
return SWAP_IMPL.swapToLimitAmounts(
|
return SWAP_IMPL.swapToLimitAmounts(
|
||||||
inputTokenIndex, outputTokenIndex, limitPrice,
|
inputTokenIndex, outputTokenIndex, limitPrice,
|
||||||
pool.denominators(), pool.kappa(), lmsr.qInternal, pool.swapFeePpm());
|
pool.denominators(), pool.bFixed(), lmsr.qInternal, pool.swapFeePpm());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,22 @@ contract GasTest is Test {
|
|||||||
uint256 constant internal INIT_BAL = 1_000_000; // initial token units for each token (internal==amount when base==1)
|
uint256 constant internal INIT_BAL = 1_000_000; // initial token units for each token (internal==amount when base==1)
|
||||||
uint256 constant internal BASE = 1; // use base=1 so internal amounts correspond to raw integers (Q64.64 units)
|
uint256 constant internal BASE = 1; // use base=1 so internal amounts correspond to raw integers (Q64.64 units)
|
||||||
|
|
||||||
|
// Compute fixed b from a target slippage profile for a pool that will be initialized
|
||||||
|
// with numTokens tokens each deposited with INIT_BAL and BASE==1.
|
||||||
|
function _computeFixedB(uint256 numTokens) internal view returns (int128) {
|
||||||
|
// Size metric S = sum q_i (in 64.64) with q_i = INIT_BAL for each token (BASE == 1)
|
||||||
|
int128 S = ABDKMath64x64.fromUInt(numTokens * INIT_BAL);
|
||||||
|
// E = (1 - s*(n-1)) / (1 + s)
|
||||||
|
int128 one = ABDKMath64x64.fromInt(1);
|
||||||
|
int128 nMinus1 = ABDKMath64x64.fromUInt(numTokens - 1);
|
||||||
|
int128 numerator = one.sub(targetSlippage.mul(nMinus1));
|
||||||
|
int128 denominator = one.add(targetSlippage);
|
||||||
|
int128 E = numerator.div(denominator);
|
||||||
|
// y = -ln(E) / f, b = S / y
|
||||||
|
int128 y = ABDKMath64x64.ln(E).neg().div(tradeFrac);
|
||||||
|
return S.div(y);
|
||||||
|
}
|
||||||
|
|
||||||
/// @notice Helper function to create a pool with the specified number of _tokens
|
/// @notice Helper function to create a pool with the specified number of _tokens
|
||||||
function createPool(uint256 numTokens) internal returns (PartyPool) {
|
function createPool(uint256 numTokens) internal returns (PartyPool) {
|
||||||
// Deploy _tokens dynamically
|
// Deploy _tokens dynamically
|
||||||
@@ -139,9 +155,9 @@ contract GasTest is Test {
|
|||||||
for (uint i = 0; i < tokens.length; i++) {
|
for (uint i = 0; i < tokens.length; i++) {
|
||||||
ierc20Tokens[i] = IERC20(tokens[i]);
|
ierc20Tokens[i] = IERC20(tokens[i]);
|
||||||
}
|
}
|
||||||
// Compute kappa from slippage params and number of _tokens, then construct pool with kappa
|
// Compute fixed b from slippage and expected initial S, then construct pool with fixed b
|
||||||
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage);
|
int128 bFixed = _computeFixedB(ierc20Tokens.length);
|
||||||
PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, false);
|
PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, bases, bFixed, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Transfer initial deposit amounts into pool before initial mint
|
// Transfer initial deposit amounts into pool before initial mint
|
||||||
for (uint256 i = 0; i < numTokens; i++) {
|
for (uint256 i = 0; i < numTokens; i++) {
|
||||||
@@ -180,8 +196,8 @@ contract GasTest is Test {
|
|||||||
for (uint i = 0; i < tokens.length; i++) {
|
for (uint i = 0; i < tokens.length; i++) {
|
||||||
ierc20Tokens[i] = IERC20(tokens[i]);
|
ierc20Tokens[i] = IERC20(tokens[i]);
|
||||||
}
|
}
|
||||||
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage);
|
int128 bFixed = _computeFixedB(ierc20Tokens.length);
|
||||||
PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, true);
|
PartyPool newPool = Deploy.newPartyPool(address(this), poolName, poolName, ierc20Tokens, bases, bFixed, feePpm, feePpm, true);
|
||||||
|
|
||||||
// Transfer initial deposit amounts into pool before initial mint
|
// Transfer initial deposit amounts into pool before initial mint
|
||||||
for (uint256 i = 0; i < numTokens; i++) {
|
for (uint256 i = 0; i < numTokens; i++) {
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[2] = ABDKMath64x64.fromUInt(1_000_000);
|
q[2] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 b = _computeBFromSlippage(3, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAlmostBalanced() internal {
|
function initAlmostBalanced() internal {
|
||||||
@@ -38,7 +39,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
q[0] = ABDKMath64x64.fromUInt(999_999);
|
q[0] = ABDKMath64x64.fromUInt(999_999);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[2] = ABDKMath64x64.fromUInt(1_000_001);
|
q[2] = ABDKMath64x64.fromUInt(1_000_001);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 b = _computeBFromSlippage(3, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initImbalanced() internal {
|
function initImbalanced() internal {
|
||||||
@@ -47,7 +49,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
q[1] = ABDKMath64x64.fromUInt(1e9);
|
q[1] = ABDKMath64x64.fromUInt(1e9);
|
||||||
q[2] = ABDKMath64x64.fromUInt(1);
|
q[2] = ABDKMath64x64.fromUInt(1);
|
||||||
q[3] = ABDKMath64x64.divu(1, 1e9);
|
q[3] = ABDKMath64x64.divu(1, 1e9);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 b = _computeBFromSlippage(4, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -193,7 +196,6 @@ contract LMSRStabilizedTest is Test {
|
|||||||
|
|
||||||
// Verify basic state is still functional
|
// Verify basic state is still functional
|
||||||
assertTrue(s.nAssets > 0, "State should still be initialized");
|
assertTrue(s.nAssets > 0, "State should still be initialized");
|
||||||
assertTrue(s.kappa > int128(0), "Kappa should still be positive");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testRescalingAfterDeposit() public {
|
function testRescalingAfterDeposit() public {
|
||||||
@@ -211,7 +213,6 @@ contract LMSRStabilizedTest is Test {
|
|||||||
|
|
||||||
// Store initial parameters
|
// Store initial parameters
|
||||||
int128 initialB = _computeB(initialQ);
|
int128 initialB = _computeB(initialQ);
|
||||||
int128 initialKappa = s.kappa;
|
|
||||||
|
|
||||||
// Simulate a deposit by increasing all asset quantities by 50%
|
// Simulate a deposit by increasing all asset quantities by 50%
|
||||||
int128[] memory newQ = new int128[](s.nAssets);
|
int128[] memory newQ = new int128[](s.nAssets);
|
||||||
@@ -223,18 +224,15 @@ contract LMSRStabilizedTest is Test {
|
|||||||
// Apply the update for proportional change
|
// Apply the update for proportional change
|
||||||
s.updateForProportionalChange(newQ);
|
s.updateForProportionalChange(newQ);
|
||||||
|
|
||||||
// Verify that b has been rescaled proportionally
|
// Verify that b remains constant after proportional deposit (fixed-b model)
|
||||||
int128 newB = _computeB(s.qInternal);
|
int128 newB = _computeB(s.qInternal);
|
||||||
int128 expectedRatio = ABDKMath64x64.fromUInt(3).div(ABDKMath64x64.fromUInt(2)); // 1.5x
|
int128 expectedRatio = ABDKMath64x64.fromInt(1); // invariant b
|
||||||
int128 actualRatio = newB.div(initialB);
|
int128 actualRatio = newB.div(initialB);
|
||||||
|
|
||||||
int128 tolerance = ABDKMath64x64.divu(1, 1000); // 0.1% tolerance
|
int128 tolerance = ABDKMath64x64.divu(1, 1000); // 0.1% tolerance
|
||||||
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b did not scale proportionally after deposit");
|
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b should remain constant after deposit");
|
||||||
|
|
||||||
// Verify kappa remained unchanged
|
// Perform a trade and verify outputs are reasonable
|
||||||
assertTrue((s.kappa.sub(initialKappa)).abs() < tolerance, "kappa should not change after deposit");
|
|
||||||
|
|
||||||
// Verify slippage target is still met by performing a trade
|
|
||||||
int128 tradeAmount = s.qInternal[0].mul(stdTradeSize);
|
int128 tradeAmount = s.qInternal[0].mul(stdTradeSize);
|
||||||
(int128 amountIn, int128 amountOut) = s.swapAmountsForExactInput(0, 1, tradeAmount, 0);
|
(int128 amountIn, int128 amountOut) = s.swapAmountsForExactInput(0, 1, tradeAmount, 0);
|
||||||
|
|
||||||
@@ -250,8 +248,10 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128 slippage = slippageRatio.sub(ABDKMath64x64.fromInt(1));
|
int128 slippage = slippageRatio.sub(ABDKMath64x64.fromInt(1));
|
||||||
console2.log('post-deposit slippage', slippage);
|
console2.log('post-deposit slippage', slippage);
|
||||||
|
|
||||||
int128 relativeError = slippage.sub(stdSlippage).abs().div(stdSlippage);
|
// With fixed b, theoretical slippage is exp(a/b) - 1
|
||||||
assertLt(relativeError, ABDKMath64x64.divu(1, 100), "Slippage target not met after deposit");
|
int128 expectedSlippage = _exp(tradeAmount.div(newB)).sub(ABDKMath64x64.fromInt(1));
|
||||||
|
int128 slippageError = (slippage.sub(expectedSlippage)).abs();
|
||||||
|
assertLt(slippageError, ABDKMath64x64.divu(1, 1_000_000), "Observed slippage deviates from model");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Test balanced2 handling of limitPrice that causes truncation of input a
|
/// @notice Test balanced2 handling of limitPrice that causes truncation of input a
|
||||||
@@ -260,7 +260,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128[] memory q = new int128[](2);
|
int128[] memory q = new int128[](2);
|
||||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, bInit);
|
||||||
|
|
||||||
// Compute b for constructing meaningful a and limits
|
// Compute b for constructing meaningful a and limits
|
||||||
int128 b = _computeB(q);
|
int128 b = _computeB(q);
|
||||||
@@ -296,7 +297,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128[] memory q = new int128[](2);
|
int128[] memory q = new int128[](2);
|
||||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, bInit);
|
||||||
|
|
||||||
// Small input a
|
// Small input a
|
||||||
int128 a = q[0].mul(ABDKMath64x64.divu(1, 1000)); // 0.1% of asset
|
int128 a = q[0].mul(ABDKMath64x64.divu(1, 1000)); // 0.1% of asset
|
||||||
@@ -326,7 +328,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128[] memory q = new int128[](2);
|
int128[] memory q = new int128[](2);
|
||||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, bInit);
|
||||||
|
|
||||||
int128 limitPrice = ABDKMath64x64.fromInt(1); // equal to current price
|
int128 limitPrice = ABDKMath64x64.fromInt(1); // equal to current price
|
||||||
|
|
||||||
@@ -359,7 +362,6 @@ contract LMSRStabilizedTest is Test {
|
|||||||
|
|
||||||
// Store initial parameters
|
// Store initial parameters
|
||||||
int128 initialB = _computeB(initialQ);
|
int128 initialB = _computeB(initialQ);
|
||||||
int128 initialKappa = s.kappa;
|
|
||||||
|
|
||||||
// Simulate a withdrawal by decreasing all asset quantities by 30%
|
// Simulate a withdrawal by decreasing all asset quantities by 30%
|
||||||
int128[] memory newQ = new int128[](s.nAssets);
|
int128[] memory newQ = new int128[](s.nAssets);
|
||||||
@@ -371,18 +373,15 @@ contract LMSRStabilizedTest is Test {
|
|||||||
// Apply the update for proportional change
|
// Apply the update for proportional change
|
||||||
s.updateForProportionalChange(newQ);
|
s.updateForProportionalChange(newQ);
|
||||||
|
|
||||||
// Verify that b has been rescaled proportionally
|
// Verify that b remains constant after proportional withdrawal (fixed-b model)
|
||||||
int128 newB = _computeB(s.qInternal);
|
int128 newB = _computeB(s.qInternal);
|
||||||
int128 expectedRatio = ABDKMath64x64.fromUInt(7).div(ABDKMath64x64.fromUInt(10)); // 0.7x
|
int128 expectedRatio = ABDKMath64x64.fromInt(1); // invariant b
|
||||||
int128 actualRatio = newB.div(initialB);
|
int128 actualRatio = newB.div(initialB);
|
||||||
|
|
||||||
int128 tolerance = ABDKMath64x64.divu(1, 1000); // 0.1% tolerance
|
int128 tolerance = ABDKMath64x64.divu(1, 1000); // 0.1% tolerance
|
||||||
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b did not scale proportionally after withdrawal");
|
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b should remain constant after withdrawal");
|
||||||
|
|
||||||
// Verify kappa remained unchanged
|
// Perform a trade and verify outputs are reasonable
|
||||||
assertTrue((s.kappa.sub(initialKappa)).abs() < tolerance, "kappa should not change after withdrawal");
|
|
||||||
|
|
||||||
// Verify slippage target is still met by performing a trade
|
|
||||||
int128 tradeAmount = s.qInternal[0].mul(stdTradeSize);
|
int128 tradeAmount = s.qInternal[0].mul(stdTradeSize);
|
||||||
(int128 amountIn, int128 amountOut) = s.swapAmountsForExactInput(0, 1, tradeAmount, 0);
|
(int128 amountIn, int128 amountOut) = s.swapAmountsForExactInput(0, 1, tradeAmount, 0);
|
||||||
|
|
||||||
@@ -398,8 +397,10 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128 slippage = slippageRatio.sub(ABDKMath64x64.fromInt(1));
|
int128 slippage = slippageRatio.sub(ABDKMath64x64.fromInt(1));
|
||||||
console2.log('post-withdrawal slippage', slippage);
|
console2.log('post-withdrawal slippage', slippage);
|
||||||
|
|
||||||
int128 relativeError = slippage.sub(stdSlippage).abs().div(stdSlippage);
|
// With fixed b, theoretical slippage is exp(a/b) - 1
|
||||||
assertLt(relativeError, ABDKMath64x64.divu(1, 100), "Slippage target not met after withdrawal");
|
int128 expectedSlippage = _exp(tradeAmount.div(newB)).sub(ABDKMath64x64.fromInt(1));
|
||||||
|
int128 slippageError = (slippage.sub(expectedSlippage)).abs();
|
||||||
|
assertLt(slippageError, ABDKMath64x64.divu(1, 1_000_000), "Observed slippage deviates from model");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- tests probing numerical stability and boundary conditions ---
|
// --- tests probing numerical stability and boundary conditions ---
|
||||||
@@ -431,8 +432,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
this.externalSwapAmountsForExactInput(0, 1, tradeAmount, ABDKMath64x64.fromInt(1));
|
this.externalSwapAmountsForExactInput(0, 1, tradeAmount, ABDKMath64x64.fromInt(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice If e_j == 0 we should revert early to avoid div-by-zero
|
/// @notice If q_j == 0, kernel should still handle computation without revert (wrapper enforces caps)
|
||||||
function testEJZeroReverts() public {
|
function testZeroQuantityOutputAssetDoesNotRevert() public {
|
||||||
initBalanced();
|
initBalanced();
|
||||||
|
|
||||||
// Create mock qInternal where asset 1 has zero quantity
|
// Create mock qInternal where asset 1 has zero quantity
|
||||||
@@ -446,8 +447,10 @@ contract LMSRStabilizedTest is Test {
|
|||||||
|
|
||||||
int128 tradeAmount = mockQInternal[0].mul(stdTradeSize);
|
int128 tradeAmount = mockQInternal[0].mul(stdTradeSize);
|
||||||
|
|
||||||
vm.expectRevert(bytes("LMSR: e_j==0"));
|
// Should not revert; exact-input uses full input and returns a defined output
|
||||||
this.externalSwapAmountsForExactInput(0, 1, tradeAmount, 0);
|
(int128 usedIn, int128 outAmt) = this.externalSwapAmountsForExactInput(0, 1, tradeAmount, 0);
|
||||||
|
assertEq(usedIn, tradeAmount, "exact-input should consume full input without limit");
|
||||||
|
assertTrue(outAmt >= 0, "output amount should be non-negative when q_j == 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice swapAmountsForPriceLimit returns zero if limit equals current price
|
/// @notice swapAmountsForPriceLimit returns zero if limit equals current price
|
||||||
@@ -534,18 +537,16 @@ contract LMSRStabilizedTest is Test {
|
|||||||
this.externalSwapAmountsForExactInput(0, 1, a, 0);
|
this.externalSwapAmountsForExactInput(0, 1, a, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to compute b from qInternal (either from provided array or state)
|
// Helper function to fetch fixed b (independent of qInternal)
|
||||||
function _computeB(int128[] memory qInternal) internal view returns (int128) {
|
function _computeB(int128[] memory qInternal) internal view returns (int128) {
|
||||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
// silence unused warning for qInternal in tests
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
qInternal;
|
||||||
return s.kappa.mul(sizeMetric);
|
return s.bFixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overload that uses state's cached qInternal
|
// Overload that uses state's fixed b
|
||||||
function _computeB() internal view returns (int128) {
|
function _computeB() internal view returns (int128) {
|
||||||
int128 sizeMetric = _computeSizeMetric(s.qInternal);
|
return s.bFixed;
|
||||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
|
||||||
return s.kappa.mul(sizeMetric);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to compute size metric (sum of all asset quantities)
|
// Helper function to compute size metric (sum of all asset quantities)
|
||||||
@@ -558,6 +559,41 @@ contract LMSRStabilizedTest is Test {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local helper: compute fixed b from a target slippage profile.
|
||||||
|
// For a trade of fraction f of S with target slippage s across n assets:
|
||||||
|
// E = (1 - s*(n-1)) / (1 + s)
|
||||||
|
// y = -ln(E) / f
|
||||||
|
// b = S / y
|
||||||
|
function _computeBFromSlippage(
|
||||||
|
uint256 nAssets,
|
||||||
|
int128[] memory qInternal,
|
||||||
|
int128 tradeFrac,
|
||||||
|
int128 targetSlippage
|
||||||
|
) internal pure returns (int128) {
|
||||||
|
require(nAssets > 1, "test: n>1");
|
||||||
|
int128 S = _computeSizeMetric(qInternal);
|
||||||
|
require(S > int128(0), "test: S<=0");
|
||||||
|
|
||||||
|
int128 f = tradeFrac;
|
||||||
|
require(f > int128(0) && f < ABDKMath64x64.fromInt(1), "test: f out of range");
|
||||||
|
|
||||||
|
int128 one = ABDKMath64x64.fromInt(1);
|
||||||
|
int128 nMinus1 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||||
|
|
||||||
|
// E must be in (0,1)
|
||||||
|
int128 numerator = one.sub(targetSlippage.mul(nMinus1)); // 1 - s*(n-1)
|
||||||
|
int128 denominator = one.add(targetSlippage); // 1 + s
|
||||||
|
require(numerator > int128(0), "test: bad slippage");
|
||||||
|
|
||||||
|
int128 E = numerator.div(denominator);
|
||||||
|
require(E > int128(0) && E < one, "test: E out of range");
|
||||||
|
|
||||||
|
int128 y = ABDKMath64x64.ln(E).neg().div(f);
|
||||||
|
require(y > int128(0), "test: y<=0");
|
||||||
|
|
||||||
|
return S.div(y);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to update the state's cached qInternal
|
// Helper function to update the state's cached qInternal
|
||||||
function _updateCachedQInternal(int128[] memory mockQInternal) internal {
|
function _updateCachedQInternal(int128[] memory mockQInternal) internal {
|
||||||
// First ensure qInternal array exists with the right size
|
// First ensure qInternal array exists with the right size
|
||||||
@@ -955,7 +991,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128[] memory q = new int128[](2);
|
int128[] memory q = new int128[](2);
|
||||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, bInit);
|
||||||
|
|
||||||
// Small trade (well within u <= 0.5 and delta <= 1%)
|
// Small trade (well within u <= 0.5 and delta <= 1%)
|
||||||
int128 a = q[0].mul(ABDKMath64x64.divu(1, 1000)); // 0.1% of asset
|
int128 a = q[0].mul(ABDKMath64x64.divu(1, 1000)); // 0.1% of asset
|
||||||
@@ -986,7 +1023,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128[] memory q = new int128[](2);
|
int128[] memory q = new int128[](2);
|
||||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, bInit);
|
||||||
|
|
||||||
// Prepare newQ starting from equal quantities; we'll grow q0 until delta > DELTA_MAX
|
// Prepare newQ starting from equal quantities; we'll grow q0 until delta > DELTA_MAX
|
||||||
int128[] memory newQ = new int128[](2);
|
int128[] memory newQ = new int128[](2);
|
||||||
@@ -1045,7 +1083,8 @@ contract LMSRStabilizedTest is Test {
|
|||||||
int128[] memory q = new int128[](2);
|
int128[] memory q = new int128[](2);
|
||||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||||
s.init(q, stdTradeSize, stdSlippage);
|
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||||
|
s.init(q, bInit);
|
||||||
|
|
||||||
// Compute b
|
// Compute b
|
||||||
int128 b = _computeB(q);
|
int128 b = _computeB(q);
|
||||||
|
|||||||
@@ -53,6 +53,18 @@ contract NativeTest is Test {
|
|||||||
uint256 constant INIT_BAL = 1_000_000; // initial token units for each token
|
uint256 constant INIT_BAL = 1_000_000; // initial token units for each token
|
||||||
uint256 constant BASE = 1; // use base=1 so internal amounts correspond to raw integers
|
uint256 constant BASE = 1; // use base=1 so internal amounts correspond to raw integers
|
||||||
|
|
||||||
|
// Compute fixed b for a pool initialized with numTokens tokens, each deposited with INIT_BAL (BASE == 1).
|
||||||
|
function _computeFixedB(uint256 numTokens) internal view returns (int128) {
|
||||||
|
int128 S = ABDKMath64x64.fromUInt(numTokens * INIT_BAL);
|
||||||
|
int128 one = ABDKMath64x64.fromInt(1);
|
||||||
|
int128 nMinus1 = ABDKMath64x64.fromUInt(numTokens - 1);
|
||||||
|
int128 numerator = one.sub(targetSlippage.mul(nMinus1));
|
||||||
|
int128 denominator = one.add(targetSlippage);
|
||||||
|
int128 E = numerator.div(denominator);
|
||||||
|
int128 y = ABDKMath64x64.ln(E).neg().div(tradeFrac);
|
||||||
|
return S.div(y);
|
||||||
|
}
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
alice = address(0xA11ce);
|
alice = address(0xA11ce);
|
||||||
bob = address(0xB0b);
|
bob = address(0xB0b);
|
||||||
@@ -93,8 +105,8 @@ contract NativeTest is Test {
|
|||||||
// Deploy pool with a small fee (0.1%)
|
// Deploy pool with a small fee (0.1%)
|
||||||
uint256 feePpm = 1000;
|
uint256 feePpm = 1000;
|
||||||
|
|
||||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
int128 bFixed = _computeFixedB(tokens.length);
|
||||||
pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, bases, kappa, feePpm, feePpm, weth, false);
|
pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, bases, bFixed, feePpm, feePpm, weth, false);
|
||||||
|
|
||||||
// Transfer initial deposit amounts into pool
|
// Transfer initial deposit amounts into pool
|
||||||
token0.transfer(address(pool), INIT_BAL);
|
token0.transfer(address(pool), INIT_BAL);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {LMSRStabilized} from "../src/LMSRStabilized.sol";
|
|||||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||||
import {PartyPool} from "../src/PartyPool.sol";
|
import {PartyPool} from "../src/PartyPool.sol";
|
||||||
import {MockERC20} from "./PartyPlanner.t.sol";
|
import {MockERC20} from "./PartyPlanner.t.sol";
|
||||||
|
import "@abdk/ABDKMath64x64.sol";
|
||||||
|
|
||||||
// Mock ERC20 token for testing
|
// Mock ERC20 token for testing
|
||||||
contract MockERC20 is ERC20 {
|
contract MockERC20 is ERC20 {
|
||||||
@@ -34,11 +35,37 @@ contract MockERC20 is ERC20 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contract PartyPlannerTest is Test {
|
contract PartyPlannerTest is Test {
|
||||||
|
using ABDKMath64x64 for int128;
|
||||||
|
|
||||||
PartyPlanner public planner;
|
PartyPlanner public planner;
|
||||||
MockERC20 public tokenA;
|
MockERC20 public tokenA;
|
||||||
MockERC20 public tokenB;
|
MockERC20 public tokenB;
|
||||||
MockERC20 public tokenC;
|
MockERC20 public tokenC;
|
||||||
|
|
||||||
|
function _computeFixedBFromDeposits(
|
||||||
|
uint256 nAssets,
|
||||||
|
uint256[] memory bases,
|
||||||
|
uint256[] memory initialDeposits,
|
||||||
|
int128 tradeFrac,
|
||||||
|
int128 targetSlippage
|
||||||
|
) internal pure returns (int128) {
|
||||||
|
require(bases.length == initialDeposits.length && bases.length == nAssets, "length mismatch");
|
||||||
|
// Compute S = sum(deposit_i / base_i) in Q64.64
|
||||||
|
int128 S = 0;
|
||||||
|
for (uint256 i = 0; i < nAssets; i++) {
|
||||||
|
S = S + ABDKMath64x64.divu(initialDeposits[i], bases[i]);
|
||||||
|
}
|
||||||
|
// E = (1 - s*(n-1)) / (1 + s)
|
||||||
|
int128 one = ABDKMath64x64.fromInt(1);
|
||||||
|
int128 nMinus1 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||||
|
int128 numerator = one.sub(targetSlippage.mul(nMinus1));
|
||||||
|
int128 denominator = one.add(targetSlippage);
|
||||||
|
int128 E = numerator.div(denominator);
|
||||||
|
// y = -ln(E) / f, b = S / y
|
||||||
|
int128 y = ABDKMath64x64.ln(E).neg().div(tradeFrac);
|
||||||
|
return S.div(y);
|
||||||
|
}
|
||||||
|
|
||||||
address public payer = makeAddr("payer");
|
address public payer = makeAddr("payer");
|
||||||
address public receiver = makeAddr("receiver");
|
address public receiver = makeAddr("receiver");
|
||||||
|
|
||||||
@@ -93,15 +120,15 @@ contract PartyPlannerTest is Test {
|
|||||||
uint256 initialTokenACount = planner.poolsByTokenCount(IERC20(address(tokenA)));
|
uint256 initialTokenACount = planner.poolsByTokenCount(IERC20(address(tokenA)));
|
||||||
uint256 initialTokenBCount = planner.poolsByTokenCount(IERC20(address(tokenB)));
|
uint256 initialTokenBCount = planner.poolsByTokenCount(IERC20(address(tokenB)));
|
||||||
|
|
||||||
// Compute kappa then create pool via kappa overload
|
// Compute fixed b from slippage and initial deposits, then create pool
|
||||||
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
int128 bFixed = _computeFixedBFromDeposits(tokens.length, bases, initialDeposits, tradeFrac, targetSlippage);
|
||||||
|
|
||||||
(IPartyPool pool, uint256 lpAmount) = planner.newPool(
|
(IPartyPool pool, uint256 lpAmount) = planner.newPool(
|
||||||
name,
|
name,
|
||||||
symbol,
|
symbol,
|
||||||
tokens,
|
tokens,
|
||||||
bases,
|
bases,
|
||||||
computedKappa,
|
bFixed,
|
||||||
swapFeePpm,
|
swapFeePpm,
|
||||||
flashFeePpm,
|
flashFeePpm,
|
||||||
false, // not stable
|
false, // not stable
|
||||||
@@ -174,10 +201,10 @@ contract PartyPlannerTest is Test {
|
|||||||
deposits1[0] = INITIAL_DEPOSIT_AMOUNT;
|
deposits1[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
deposits1[1] = INITIAL_DEPOSIT_AMOUNT;
|
deposits1[1] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
|
|
||||||
int128 kappa1 = LMSRStabilized.computeKappaFromSlippage(tokens1.length, int128((1 << 64) - 1), int128(1 << 62));
|
int128 b1 = _computeFixedBFromDeposits(tokens1.length, bases1, deposits1, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
(IPartyPool pool1,) = planner.newPool(
|
(IPartyPool pool1,) = planner.newPool(
|
||||||
"Pool 1", "LP1", tokens1, bases1,
|
"Pool 1", "LP1", tokens1, bases1,
|
||||||
kappa1, 3000, 5000, false,
|
b1, 3000, 5000, false,
|
||||||
payer, receiver, deposits1, 1000e18, 0
|
payer, receiver, deposits1, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -194,10 +221,10 @@ contract PartyPlannerTest is Test {
|
|||||||
deposits2[0] = INITIAL_DEPOSIT_AMOUNT;
|
deposits2[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
deposits2[1] = INITIAL_DEPOSIT_AMOUNT / 1e12; // Adjust for 6 decimals
|
deposits2[1] = INITIAL_DEPOSIT_AMOUNT / 1e12; // Adjust for 6 decimals
|
||||||
|
|
||||||
int128 kappa2 = LMSRStabilized.computeKappaFromSlippage(tokens2.length, int128((1 << 64) - 1), int128(1 << 62));
|
int128 b2 = _computeFixedBFromDeposits(tokens2.length, bases2, deposits2, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
(IPartyPool pool2,) = planner.newPool(
|
(IPartyPool pool2,) = planner.newPool(
|
||||||
"Pool 2", "LP2", tokens2, bases2,
|
"Pool 2", "LP2", tokens2, bases2,
|
||||||
kappa2, 3000, 5000, false,
|
b2, 3000, 5000, false,
|
||||||
payer, receiver, deposits2, 1000e18, 0
|
payer, receiver, deposits2, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -250,12 +277,12 @@ contract PartyPlannerTest is Test {
|
|||||||
validDeposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
validDeposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
validDeposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
validDeposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
|
|
||||||
int128 kappaErr = LMSRStabilized.computeKappaFromSlippage(tokens.length, int128((1 << 64) - 1), int128(1 << 62));
|
int128 bErr = _computeFixedBFromDeposits(tokens.length, bases, validDeposits, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
|
|
||||||
vm.expectRevert("Planner: payer cannot be zero address");
|
vm.expectRevert("Planner: payer cannot be zero address");
|
||||||
planner.newPool(
|
planner.newPool(
|
||||||
"Test Pool", "TESTLP", tokens, bases,
|
"Test Pool", "TESTLP", tokens, bases,
|
||||||
kappaErr, 3000, 5000, false,
|
bErr, 3000, 5000, false,
|
||||||
address(0), receiver, validDeposits, 1000e18, 0
|
address(0), receiver, validDeposits, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -263,18 +290,18 @@ contract PartyPlannerTest is Test {
|
|||||||
vm.expectRevert("Planner: receiver cannot be zero address");
|
vm.expectRevert("Planner: receiver cannot be zero address");
|
||||||
planner.newPool(
|
planner.newPool(
|
||||||
"Test Pool", "TESTLP", tokens, bases,
|
"Test Pool", "TESTLP", tokens, bases,
|
||||||
kappaErr, 3000, 5000, false,
|
bErr, 3000, 5000, false,
|
||||||
payer, address(0), validDeposits, 1000e18, 0
|
payer, address(0), validDeposits, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test deadline exceeded
|
// Test deadline exceeded
|
||||||
// The default timestamp is 1 and 1-0 is 0 which means "ignore deadline," so we need to set a proper timestamp.
|
// The default timestamp is 1 and 1-0 is 0 which means "ignore deadline," so we need to set a proper timestamp.
|
||||||
int128 kappaDeadline = LMSRStabilized.computeKappaFromSlippage(tokens.length, int128((1 << 64) - 1), int128(1 << 62));
|
int128 bDeadline = _computeFixedBFromDeposits(tokens.length, bases, validDeposits, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
vm.warp(1000);
|
vm.warp(1000);
|
||||||
vm.expectRevert("Planner: deadline exceeded");
|
vm.expectRevert("Planner: deadline exceeded");
|
||||||
planner.newPool(
|
planner.newPool(
|
||||||
"Test Pool", "TESTLP", tokens, bases,
|
"Test Pool", "TESTLP", tokens, bases,
|
||||||
kappaDeadline, 3000, 5000, false,
|
bDeadline, 3000, 5000, false,
|
||||||
payer, receiver, validDeposits, 1000e18, block.timestamp - 1
|
payer, receiver, validDeposits, 1000e18, block.timestamp - 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -297,12 +324,12 @@ contract PartyPlannerTest is Test {
|
|||||||
deposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
deposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
deposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
deposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
|
|
||||||
int128 kappaLoop = LMSRStabilized.computeKappaFromSlippage(tokens.length, int128((1 << 64) - 1), int128(1 << 62));
|
int128 bLoop = _computeFixedBFromDeposits(tokens.length, bases, deposits, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
(IPartyPool pool,) = planner.newPool(
|
(IPartyPool pool,) = planner.newPool(
|
||||||
string(abi.encodePacked("Pool ", vm.toString(i))),
|
string(abi.encodePacked("Pool ", vm.toString(i))),
|
||||||
string(abi.encodePacked("LP", vm.toString(i))),
|
string(abi.encodePacked("LP", vm.toString(i))),
|
||||||
tokens, bases,
|
tokens, bases,
|
||||||
kappaLoop, 3000, 5000, false,
|
bLoop, 3000, 5000, false,
|
||||||
payer, receiver, deposits, 1000e18, 0
|
payer, receiver, deposits, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,18 @@ contract PartyPoolTest is Test {
|
|||||||
uint256 constant INIT_BAL = 1_000_000; // initial token units for each token (internal==amount when base==1)
|
uint256 constant INIT_BAL = 1_000_000; // initial token units for each token (internal==amount when base==1)
|
||||||
uint256 constant BASE = 1; // use base=1 so internal amounts correspond to raw integers (Q64.64 units)
|
uint256 constant BASE = 1; // use base=1 so internal amounts correspond to raw integers (Q64.64 units)
|
||||||
|
|
||||||
|
// Compute fixed b for a pool initialized with numTokens tokens, each deposited with INIT_BAL (BASE == 1).
|
||||||
|
function _computeFixedB(uint256 numTokens) internal view returns (int128) {
|
||||||
|
int128 S = ABDKMath64x64.fromUInt(numTokens * INIT_BAL);
|
||||||
|
int128 one = ABDKMath64x64.fromInt(1);
|
||||||
|
int128 nMinus1 = ABDKMath64x64.fromUInt(numTokens - 1);
|
||||||
|
int128 numerator = one.sub(targetSlippage.mul(nMinus1));
|
||||||
|
int128 denominator = one.add(targetSlippage);
|
||||||
|
int128 E = numerator.div(denominator);
|
||||||
|
int128 y = ABDKMath64x64.ln(E).neg().div(tradeFrac);
|
||||||
|
return S.div(y);
|
||||||
|
}
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
planner = Deploy.newPartyPlanner();
|
planner = Deploy.newPartyPlanner();
|
||||||
alice = address(0xA11ce);
|
alice = address(0xA11ce);
|
||||||
@@ -172,8 +184,8 @@ contract PartyPoolTest is Test {
|
|||||||
// Deploy pool with a small fee to test fee-handling paths (use 1000 ppm = 0.1%)
|
// Deploy pool with a small fee to test fee-handling paths (use 1000 ppm = 0.1%)
|
||||||
uint256 feePpm = 1000;
|
uint256 feePpm = 1000;
|
||||||
|
|
||||||
int128 kappa3 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
int128 b3 = _computeFixedB(tokens.length);
|
||||||
pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, bases, kappa3, feePpm, feePpm, false);
|
pool = Deploy.newPartyPool(address(this), "LP", "LP", tokens, bases, b3, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Transfer initial deposit amounts into pool before initial mint (pool expects _tokens already in contract)
|
// Transfer initial deposit amounts into pool before initial mint (pool expects _tokens already in contract)
|
||||||
// We deposit equal amounts INIT_BAL for each token
|
// We deposit equal amounts INIT_BAL for each token
|
||||||
@@ -202,8 +214,8 @@ contract PartyPoolTest is Test {
|
|||||||
bases10[i] = BASE;
|
bases10[i] = BASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int128 kappa10 = LMSRStabilized.computeKappaFromSlippage(tokens10.length, tradeFrac, targetSlippage);
|
int128 b10 = _computeFixedB(tokens10.length);
|
||||||
pool10 = Deploy.newPartyPool(address(this), "LP10", "LP10", tokens10, bases10, kappa10, feePpm, feePpm, false);
|
pool10 = Deploy.newPartyPool(address(this), "LP10", "LP10", tokens10, bases10, b10, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Mint additional _tokens for pool10 initial deposit
|
// Mint additional _tokens for pool10 initial deposit
|
||||||
token0.mint(address(this), INIT_BAL);
|
token0.mint(address(this), INIT_BAL);
|
||||||
@@ -991,12 +1003,12 @@ contract PartyPoolTest is Test {
|
|||||||
uint256 feePpm = 1000;
|
uint256 feePpm = 1000;
|
||||||
|
|
||||||
// Pool with default initialization (lpTokens = 0)
|
// Pool with default initialization (lpTokens = 0)
|
||||||
int128 kappaDefault = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
int128 bDefault = _computeFixedB(tokens.length);
|
||||||
PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault, feePpm, feePpm, false);
|
PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, bases, bDefault, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Pool with custom initialization (lpTokens = custom amount)
|
// Pool with custom initialization (lpTokens = custom amount)
|
||||||
int128 kappaCustom = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
int128 bCustom = _computeFixedB(tokens.length);
|
||||||
PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom, feePpm, feePpm, false);
|
PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, bases, bCustom, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Mint additional _tokens for both pools
|
// Mint additional _tokens for both pools
|
||||||
token0.mint(address(this), INIT_BAL * 2);
|
token0.mint(address(this), INIT_BAL * 2);
|
||||||
@@ -1067,10 +1079,10 @@ contract PartyPoolTest is Test {
|
|||||||
|
|
||||||
uint256 feePpm = 1000;
|
uint256 feePpm = 1000;
|
||||||
|
|
||||||
int128 kappaDefault2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
int128 bDefault2 = _computeFixedB(tokens.length);
|
||||||
PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault2, feePpm, feePpm, false);
|
PartyPool poolDefault = Deploy.newPartyPool(address(this), "LP_DEFAULT", "LP_DEFAULT", tokens, bases, bDefault2, feePpm, feePpm, false);
|
||||||
int128 kappaCustom2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
int128 bCustom2 = _computeFixedB(tokens.length);
|
||||||
PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom2, feePpm, feePpm, false);
|
PartyPool poolCustom = Deploy.newPartyPool(address(this), "LP_CUSTOM", "LP_CUSTOM", tokens, bases, bCustom2, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Mint additional _tokens
|
// Mint additional _tokens
|
||||||
token0.mint(address(this), INIT_BAL * 4);
|
token0.mint(address(this), INIT_BAL * 4);
|
||||||
|
|||||||
Reference in New Issue
Block a user