pool_design.py
This commit is contained in:
459
research/get_quotes.js
Normal file
459
research/get_quotes.js
Normal file
@@ -0,0 +1,459 @@
|
||||
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);
|
||||
162
research/pool_design.py
Normal file
162
research/pool_design.py
Normal file
@@ -0,0 +1,162 @@
|
||||
import logging
|
||||
import math
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def lmsr_swap_amount_out(
|
||||
balances,
|
||||
amount_in,
|
||||
token_in_index,
|
||||
token_out_index,
|
||||
lp_fee,
|
||||
kappa,
|
||||
):
|
||||
"""
|
||||
Compute the LMSR kernel fee-free amountOut for swapping `amount_in` of token `token_in_index`
|
||||
into token `token_out_index`, applying lp_fee to the input amount (i.e., amount_in_net = amount_in * (1 - lp_fee)).
|
||||
Uses native Python floats for performance and simplicity.
|
||||
|
||||
Notes on congruence with PartyPool / LMSRStabilized:
|
||||
- The kernel formula implemented here matches the LMSR closed-form:
|
||||
amountOut = b * ln(1 + r0 * (1 - exp(-a / b)))
|
||||
where r0 = exp((q_i - q_j) / b) and b = kappa * S (S = sum balances).
|
||||
- lp_fee is applied to the input before the kernel (fee-on-input), matching PartyPool's placement.
|
||||
- This function uses continuous float arithmetic and does NOT emulate
|
||||
PartyPool's integer/unit conversions or rounding (floor/ceil) and ppm fee quantization.
|
||||
|
||||
Parameters:
|
||||
- balances: iterable of per-token balances (numbers). These represent q_i in internal units.
|
||||
- amount_in: input amount supplied by swapper (before fees).
|
||||
- token_in_index: index of the input token i.
|
||||
- token_out_index: index of the output token j.
|
||||
- lp_fee: fractional LP fee applied to the input (e.g., 0.003).
|
||||
- kappa: liquidity parameter κ (must be positive, in same units as balances).
|
||||
|
||||
Returns:
|
||||
- float: net amountOut (exclusive of fees) the swapper receives from the pool (capped at pool balance).
|
||||
"""
|
||||
# Normalize and validate inputs (convert to floats)
|
||||
try:
|
||||
q = [float(x) for x in balances]
|
||||
a_in = float(amount_in)
|
||||
lpf = float(lp_fee)
|
||||
k = float(kappa)
|
||||
except (TypeError, ValueError) as e:
|
||||
raise ValueError("Invalid numeric input") from e
|
||||
|
||||
n = len(q)
|
||||
if not (0 <= token_in_index < n and 0 <= token_out_index < n):
|
||||
raise IndexError("token indices out of range")
|
||||
|
||||
if a_in <= 0.0:
|
||||
return 0.0
|
||||
|
||||
if k <= 0.0:
|
||||
raise ValueError("kappa must be positive")
|
||||
|
||||
# Size metric S = sum q_i
|
||||
S = sum(q)
|
||||
if S <= 0.0:
|
||||
raise ValueError("size metric (sum balances) must be positive")
|
||||
|
||||
# b = kappa * S
|
||||
b = k * S
|
||||
if b <= 0.0:
|
||||
raise ValueError("computed b must be positive")
|
||||
|
||||
# Apply LP fee on the input amount (kernel is fee-free; wrapper handles fees)
|
||||
if not (0.0 <= lpf < 1.0):
|
||||
raise ValueError("lp_fee must be in [0, 1)")
|
||||
a_net = a_in * (1.0 - lpf)
|
||||
if a_net <= 0.0:
|
||||
return 0.0
|
||||
|
||||
qi = q[token_in_index]
|
||||
qj = q[token_out_index]
|
||||
|
||||
# Guard: output asset must have non-zero effective reserve
|
||||
if qj <= 0.0:
|
||||
# No available output to withdraw
|
||||
return 0.0
|
||||
|
||||
# Compute r0 = exp((q_i - q_j) / b)
|
||||
try:
|
||||
r0 = math.exp((qi - qj) / b)
|
||||
except OverflowError:
|
||||
raise ArithmeticError("exponential overflow in r0 computation")
|
||||
|
||||
# Compute a/b
|
||||
a_over_b = a_net / b
|
||||
|
||||
# exp(-a/b)
|
||||
try:
|
||||
exp_neg = math.exp(-a_over_b)
|
||||
except OverflowError:
|
||||
# If exp would underflow/overflow, treat exp(-a/b) as 0 in extreme case
|
||||
exp_neg = 0.0
|
||||
|
||||
# inner = 1 + r0 * (1 - exp(-a/b))
|
||||
inner = 1.0 + r0 * (1.0 - exp_neg)
|
||||
|
||||
# If inner <= 0, cap to available balance qj
|
||||
if inner <= 0.0:
|
||||
return float(qj)
|
||||
|
||||
# amountOut = b * ln(inner)
|
||||
try:
|
||||
ln_inner = math.log(inner)
|
||||
except (ValueError, OverflowError):
|
||||
# Numeric issue computing ln; be conservative and return 0
|
||||
return 0.0
|
||||
|
||||
amount_out = b * ln_inner
|
||||
|
||||
# Safety: non-positive output -> return zero
|
||||
if amount_out <= 0.0:
|
||||
return 0.0
|
||||
|
||||
# Cap output to pool's current balance qj (cannot withdraw more than available)
|
||||
if amount_out > qj:
|
||||
return float(qj)
|
||||
|
||||
return float(amount_out)
|
||||
|
||||
|
||||
def main():
|
||||
balance0 = 1_000_000 # estimated from the production pool
|
||||
balances = [balance0, balance0]
|
||||
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')
|
||||
d.columns = ['block', 'price0', 'price1', 'in0', 'out0', 'in1', 'out1']
|
||||
uniswap_slippage = 1 - d.out0 / d.in0 / d.iloc[0].price0
|
||||
plt.plot(d.in0 / balance0, uniswap_slippage, label='CP')
|
||||
|
||||
# Interpolate Uniswap slippage to match LMSR x-coordinates
|
||||
interp_uniswap = np.interp(X / balance0, d.in0 / balance0, uniswap_slippage)
|
||||
mask = Y < interp_uniswap
|
||||
plt.fill_between(X / balance0, 0, 1, where=mask, alpha=0.2, color='green')
|
||||
|
||||
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, _: '{:.2f}%'.format(y)))
|
||||
plt.xlabel('Input Amount (basis points of initial balance)')
|
||||
plt.ylabel('Slippage')
|
||||
plt.title('Pool Slippages')
|
||||
plt.grid(True)
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user