diff --git a/gasAnalyzer/README.md b/gasAnalyzer/README.md new file mode 100644 index 0000000..5a99dd3 --- /dev/null +++ b/gasAnalyzer/README.md @@ -0,0 +1,71 @@ +# DEX Router Gas Analyzer + +A tool for analyzing gas consumption of DEX Router swap methods using `debug_traceCall`. + +## Quick Start + +### 1. Install + +```bash +cd DEX-Router-Tools-Suite/gasAnalyzer +npm install +``` + +### 2. Configure RPC + +Edit `config/chains.js` with your QuickNode RPC URL (must support `debug_traceCall`): + +```javascript +eth: { + rpcUrl: 'YOUR_QUICKNODE_URL', + // ... +} +``` + +### 3. Test RPC Connection + +```bash +node utils/quickTest.js +``` + +### 4. Run Analysis + +```bash +# Single swap type +node analyzer/dynamicGasAnalyzer.js arb dagSwap ERC20->ETH + +# All swap types +node analyzer/dynamicGasAnalyzer.js arb dagSwap all + +# With UniV3 pool (for dagSwap) +POOL_TYPE=uniV3 node analyzer/dynamicGasAnalyzer.js arb dagSwap all +``` + +## Supported Methods + +| Method | Command | +|--------|---------| +| dagSwapByOrderId | `node analyzer/dynamicGasAnalyzer.js {chain} dagSwap {swapType}` | +| unxswapByOrderId | `node analyzer/dynamicGasAnalyzer.js {chain} unxSwap {swapType}` | +| uniswapV3SwapTo | `node analyzer/dynamicGasAnalyzer.js {chain} uniswapV3 {swapType}` | + +## Swap Types + +- **Swap Types**: `ERC20->ERC20`, `ETH->ERC20`, `ERC20->ETH`, `all` + +## Output + +Results are saved to `result/` folder: +- CSV: `gas-analysis-{chain}-{method}-{timestamp}.csv` +- JSON: `gas-analysis-{chain}-{method}-{timestamp}.json` + +## Project Structure + +``` +gasAnalyzer/ +├── config/ # Chain & pool configurations +├── encoder/ # Calldata encoding +├── analyzer/ # Gas analysis scripts +├── utils/ # Helper scripts +└── result/ # Output files +``` diff --git a/gasAnalyzer/analyzer/dynamicGasAnalyzer.js b/gasAnalyzer/analyzer/dynamicGasAnalyzer.js new file mode 100644 index 0000000..5674842 --- /dev/null +++ b/gasAnalyzer/analyzer/dynamicGasAnalyzer.js @@ -0,0 +1,392 @@ +const GasTracer = require('./gasTracer'); +const CalldataEncoder = require('../encoder/calldataEncoder'); +const ScenarioBuilder = require('../encoder/scenarioBuilder'); +const poolConfig = require('../config/pools'); +const config = require('../config/chains'); +const { ethers } = require('ethers'); +const fs = require('fs'); +const path = require('path'); + +/** + * Dynamic Gas Analyzer - Uses dynamically generated calldata + * No dependency on fixed example calldata + */ +class DynamicGasAnalyzer { + constructor(chain, method) { + this.chain = chain; + this.method = method; + this.tracer = new GasTracer(chain); + this.results = []; + } + + /** + * Generate state overrides for ERC20 balance + */ + generateStateOverrides() { + const chainConfig = poolConfig.pools[this.chain]; + const from = poolConfig.defaultFrom; + + // Give the sender enough USDC and approve the router + // NOTE: ethers v5 compatibility (v6 uses zeroPadValue/toBeHex/parseUnits on root) + // In v5, use utils.hexlify + utils.hexZeroPad + utils.parseUnits. + const storageSlot = ethers.utils.hexZeroPad(ethers.utils.hexlify(0), 32); + + const overrides = {}; + + // Override USDC balance + overrides[chainConfig.usdc] = { + stateDiff: { + // Balance slot (usually slot 0 or calculated) + [storageSlot]: ethers.utils.hexZeroPad( + ethers.utils.hexlify(ethers.utils.parseUnits('1000', 6)), + 32 + ) + } + }; + + return overrides; + } + + /** + * Analyze a single scenario + */ + async analyzeScenario(scenario, baseMetadata) { + console.log(`\n${'='.repeat(60)}`); + console.log(`Analyzing: ${scenario.name} - ${scenario.swapType}`); + console.log(`${'='.repeat(60)}`); + + try { + const chainConfig = config.chains[this.chain]; + + // Calculate total value: base value + extra value for fromToken commissions + let totalValue = BigInt(baseMetadata.value || '0x0'); + if (scenario.extraValue && scenario.extraValue !== '0x0') { + totalValue += BigInt(scenario.extraValue); + } + const valueHex = '0x' + totalValue.toString(16); + + const txParams = { + from: baseMetadata.from, + to: chainConfig.contract, + data: scenario.calldata, + value: valueHex + }; + + console.log(`Transaction:`); + console.log(` From: ${txParams.from}`); + console.log(` To: ${txParams.to}`); + console.log(` Value: ${txParams.value}${scenario.extraValue !== '0x0' ? ` (base: ${baseMetadata.value}, extra: ${scenario.extraValue})` : ''}`); + console.log(` Data length: ${scenario.calldata.length} chars`); + + // Trace the call + const trace = await this.tracer.traceCall(txParams, baseMetadata.blockNumber); + + // Calculate gas breakdown + const breakdown = this.tracer.calculateGasBreakdown(trace); + this.tracer.printGasBreakdown(breakdown); + + const result = { + scenario: scenario.name, + swapType: scenario.swapType, + description: scenario.description, + breakdown, + timestamp: new Date().toISOString() + }; + + this.results.push(result); + return result; + + } catch (error) { + console.error(`Error analyzing scenario ${scenario.name}:`, error.message); + return { + scenario: scenario.name, + swapType: scenario.swapType, + error: error.message + }; + } + } + + /** + * Analyze all scenarios for a specific swap type + */ + async analyzeSwapType(swapType, blockNumber = 'latest') { + console.log(`\n${'*'.repeat(60)}`); + console.log(`Analyzing: ${swapType} on ${this.chain}`); + console.log(`Method: ${this.method}`); + console.log(`${'*'.repeat(60)}`); + + // Generate base calldata + console.log('\nGenerating base calldata...'); + const baseResult = await CalldataEncoder.generate(this.method, this.chain, swapType, blockNumber); + + console.log(`✓ Base calldata generated`); + console.log(` Method: ${baseResult.metadata.method}`); + console.log(` Pool: ${baseResult.metadata.pool}`); + console.log(` Amount: ${baseResult.metadata.amount}`); + + // Generate all scenarios (pass swapAmount and chain for commission calculation) + const scenarios = ScenarioBuilder.generateAllScenarios(baseResult.calldata, swapType, baseResult.value, this.chain); + console.log(`✓ ${scenarios.length} scenarios generated\n`); + + // Analyze each scenario + for (const scenario of scenarios) { + await this.analyzeScenario(scenario, baseResult); + + // Add delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + return this.results.filter(r => r.swapType === swapType); + } + + /** + * Analyze all swap types + */ + async analyzeAllSwapTypes(blockNumber = 'latest') { + const swapTypes = ['ERC20->ERC20', 'ETH->ERC20', 'ERC20->ETH']; + + for (const swapType of swapTypes) { + await this.analyzeSwapType(swapType, blockNumber); + } + + return this.results; + } + + /** + * Generate comparison table + */ + generateComparisonTable(swapType) { + const scenarioResults = this.results.filter(r => r.swapType === swapType && !r.error); + + if (scenarioResults.length === 0) { + console.log(`No results available for ${swapType}`); + return null; + } + + console.log(`\n=== Gas Comparison Table: ${swapType} ===`); + console.log(`Method: ${config.methods[this.method].name}`); + console.log(`Chain: ${config.chains[this.chain].name}\n`); + + console.log('Scenario'.padEnd(40) + 'Before'.padEnd(12) + 'Adapters'.padEnd(12) + 'After'.padEnd(12) + 'Total'); + console.log('-'.repeat(88)); + + const basicResult = scenarioResults.find(r => r.scenario === 'basic'); + const basicGas = basicResult?.breakdown; + + scenarioResults.forEach(result => { + const b = result.breakdown; + const row = [ + result.scenario.padEnd(40), + b.beforeSwap.toLocaleString().padEnd(12), + b.totalAdapterGas.toLocaleString().padEnd(12), + b.afterSwap.toLocaleString().padEnd(12), + b.totalDecimal.toLocaleString() + ]; + console.log(row.join('')); + + if (basicGas && result.scenario !== 'basic') { + const deltaRow = [ + ' (delta from basic)'.padEnd(40), + `+${(b.beforeSwap - basicGas.beforeSwap).toLocaleString()}`.padEnd(12), + `+${(b.totalAdapterGas - basicGas.totalAdapterGas).toLocaleString()}`.padEnd(12), + `+${(b.afterSwap - basicGas.afterSwap).toLocaleString()}`.padEnd(12), + `+${(b.totalDecimal - basicGas.totalDecimal).toLocaleString()}` + ]; + console.log(deltaRow.join('')); + } + }); + + console.log('='.repeat(88) + '\n'); + + return { + swapType, + results: scenarioResults, + basic: basicGas + }; + } + + /** + * Export results to CSV + */ + exportToCSV(filename = null) { + if (this.results.length === 0) { + console.log('No results to export'); + return; + } + + // NOTE: Write outputs under result/ by default. + const resultDir = path.join(__dirname, '..', 'result'); + if (!fs.existsSync(resultDir)) { + fs.mkdirSync(resultDir, { recursive: true }); + } + + const outputFile = filename + ? filename + : path.join(resultDir, `gas-analysis-${this.chain}-${this.method}-${Date.now()}.csv`); + + const header = [ + 'Chain', + 'Method', + 'Scenario', + 'SwapType', + 'BeforeSwap', + 'TotalAdapterGas', + 'AfterSwap', + 'Total', + 'NumAdapters', + 'Timestamp' + ].join(','); + + const rows = this.results + .filter(r => !r.error) + .map(result => { + const b = result.breakdown; + return [ + this.chain, + this.method, + result.scenario, + result.swapType, + b.beforeSwap, + b.totalAdapterGas, + b.afterSwap, + b.totalDecimal, + b.adapters.length, + result.timestamp + ].join(','); + }); + + const csv = [header, ...rows].join('\n'); + + fs.writeFileSync(outputFile, csv); + console.log(`\nResults exported to CSV: ${outputFile}`); + + return outputFile; + } + + /** + * Export results to JSON + */ + exportToJSON(filename = null) { + if (this.results.length === 0) { + console.log('No results to export'); + return; + } + + // NOTE: Write outputs under result/ by default. + const resultDir = path.join(__dirname, '..', 'result'); + if (!fs.existsSync(resultDir)) { + fs.mkdirSync(resultDir, { recursive: true }); + } + + const outputFile = filename + ? filename + : path.join(resultDir, `gas-analysis-${this.chain}-${this.method}-${Date.now()}.json`); + + const exportData = { + chain: this.chain, + chainName: config.chains[this.chain].name, + contract: config.chains[this.chain].contract, + method: this.method, + methodName: config.methods[this.method].name, + timestamp: new Date().toISOString(), + results: this.results + }; + + fs.writeFileSync(outputFile, JSON.stringify(exportData, null, 2)); + console.log(`Results exported to JSON: ${outputFile}`); + + return outputFile; + } + + /** + * Generate summary report + */ + generateSummaryReport() { + console.log('\n' + '='.repeat(80)); + console.log('GAS ANALYSIS SUMMARY REPORT'.padStart(50)); + console.log('='.repeat(80)); + console.log(`Chain: ${config.chains[this.chain].name}`); + console.log(`Contract: ${config.chains[this.chain].contract}`); + console.log(`Method: ${config.methods[this.method].name}`); + console.log(`Total Scenarios Analyzed: ${this.results.filter(r => !r.error).length}`); + console.log(`Failed Scenarios: ${this.results.filter(r => r.error).length}`); + console.log('='.repeat(80)); + + const swapTypes = ['ERC20->ERC20', 'ETH->ERC20', 'ERC20->ETH']; + swapTypes.forEach(swapType => { + this.generateComparisonTable(swapType); + }); + + this.exportToCSV(); + this.exportToJSON(); + } +} + +// CLI interface +if (require.main === module) { + const args = process.argv.slice(2); + + if (args.length < 2) { + console.log('Usage: node dynamicGasAnalyzer.js [swapType] [blockNumber]'); + console.log(''); + console.log('Chains:', Object.keys(config.chains).join(', ')); + console.log('Methods:', Object.keys(config.methods).join(', ')); + console.log('SwapTypes: ERC20->ERC20, ETH->ERC20, ERC20->ETH, all'); + console.log(''); + console.log('Examples:'); + console.log(' node dynamicGasAnalyzer.js arb uniswapV3 "ERC20->ERC20"'); + console.log(' node dynamicGasAnalyzer.js arb uniswapV3 erc20 # shorthand'); + console.log(' node dynamicGasAnalyzer.js eth dagSwap all 18500000'); + console.log(''); + console.log('Note: Swap types with -> must be quoted or use shorthand:'); + console.log(' erc20 = ERC20->ERC20'); + console.log(' eth2erc20 = ETH->ERC20'); + console.log(' erc202eth = ERC20->ETH'); + process.exit(1); + } + + let [chain, method, swapTypeOrAll, blockNumber] = args; + + // Support shorthand swap types + const swapTypeShorthands = { + 'erc20': 'ERC20->ERC20', + 'eth2erc20': 'ETH->ERC20', + 'erc202eth': 'ERC20->ETH' + }; + + if (swapTypeOrAll && swapTypeShorthands[swapTypeOrAll.toLowerCase()]) { + swapTypeOrAll = swapTypeShorthands[swapTypeOrAll.toLowerCase()]; + } + + if (!config.chains[chain]) { + console.error(`Invalid chain: ${chain}`); + process.exit(1); + } + + if (!config.methods[method]) { + console.error(`Invalid method: ${method}`); + process.exit(1); + } + + (async () => { + try { + const analyzer = new DynamicGasAnalyzer(chain, method); + + if (swapTypeOrAll === 'all' || !swapTypeOrAll) { + await analyzer.analyzeAllSwapTypes(blockNumber || 'latest'); + } else { + await analyzer.analyzeSwapType(swapTypeOrAll, blockNumber || 'latest'); + } + + analyzer.generateSummaryReport(); + + } catch (error) { + console.error('Error:', error.message); + console.error(error.stack); + process.exit(1); + } + })(); +} + +module.exports = DynamicGasAnalyzer; + diff --git a/gasAnalyzer/analyzer/gasTracer.js b/gasAnalyzer/analyzer/gasTracer.js new file mode 100644 index 0000000..1fd339b --- /dev/null +++ b/gasAnalyzer/analyzer/gasTracer.js @@ -0,0 +1,248 @@ +const { ethers } = require('ethers'); +const config = require('../config/chains'); + +/** + * Gas tracer using debug_traceCall RPC method + */ +class GasTracer { + constructor(chain) { + this.chain = chain; + this.chainConfig = config.chains[chain]; + // NOTE: ethers v5 uses ethers.providers.JsonRpcProvider (v6 uses ethers.JsonRpcProvider) + this.provider = new ethers.providers.JsonRpcProvider(this.chainConfig.rpcUrl); + } + + /** + * Execute debug_traceCall to get detailed gas breakdown + */ + async traceCall(txParams, blockNumber = 'latest', stateOverrides = null) { + try { + console.log(`\nTracing call on ${this.chainConfig.name}...`); + console.log(`Contract: ${txParams.to}`); + console.log(`Block: ${blockNumber}`); + + const traceConfig = { + tracer: 'callTracer' + }; + + if (stateOverrides) { + traceConfig.stateOverrides = stateOverrides; + } + + const result = await this.provider.send('debug_traceCall', [ + txParams, + blockNumber, + traceConfig + ]); + + return result; + } catch (error) { + console.error('Error tracing call:', error.message); + + if (error.message.includes('Method not found') || error.message.includes('not supported')) { + console.error('\n⚠️ The RPC endpoint does not support debug_traceCall.'); + console.error('Please update config.js with your QuickNode endpoint.'); + console.error('\nGet QuickNode: https://www.quicknode.com/'); + } + + throw error; + } + } + + /** + * Identify swap method and adapters from trace + */ + identifySwapComponents(trace) { + const components = { + mainMethod: null, + beforeSwap: null, + adapters: [], + afterSwap: null + }; + + const inputSelector = trace.input.slice(0, 10); + + // Identify main swap method + for (const [methodKey, methodConfig] of Object.entries(config.methods)) { + if (inputSelector === methodConfig.selector) { + components.mainMethod = { + name: methodConfig.name, + selector: inputSelector, + gasUsed: parseInt(trace.gasUsed, 16) + }; + break; + } + } + + // Analyze calls to identify adapters + if (trace.calls) { + this.analyzeCallsForAdapters(trace.calls, components); + } + + return components; + } + + /** + * Analyze direct child calls to find adapters (no recursive - avoid double counting) + * dagSwap adapter already includes the gas of nested pool calls (e.g. uniswapV3 swap) + */ + analyzeCallsForAdapters(calls, components, depth = 0) { + for (const call of calls) { + const callSelector = call.input?.slice(0, 10); + + // Check if this is a known adapter + for (const [methodKey, methodConfig] of Object.entries(config.methods)) { + if (methodConfig.adapterSelectors.includes(callSelector)) { + // Only count top-level adapter calls (depth 0) to avoid double-counting + // e.g., dagSwap_adapter already includes uniswapV3 swap gas + if (depth === 0) { + components.adapters.push({ + name: `${methodKey}_adapter`, + selector: callSelector, + to: call.to, + gasUsed: parseInt(call.gasUsed, 16), + type: call.type + }); + } + break; + } + } + + // Recursively analyze nested calls (but only for detection, not for gas counting) + if (call.calls) { + this.analyzeCallsForAdapters(call.calls, components, depth + 1); + } + } + } + + /** + * Check if a call is a token query (token0/token1/balance queries) + * These are considered part of adapter operations + */ + isTokenQuery(callSelector, callTo, poolAddress) { + // Common token query selectors + const querySelectors = [ + '0xd21220a7', // token0() + '0x0dfe1681', // token1() + '0xddca3f43', // fee() + '0x70a08231' // balanceOf(address) + ]; + + // If it's a query to the pool address, it's part of adapter + if (callTo && poolAddress && callTo.toLowerCase() === poolAddress.toLowerCase()) { + return querySelectors.includes(callSelector); + } + + return false; + } + + /** + * Calculate gas breakdown for swap + * Logic: Before = gas before adapter calls (excluding token queries to pools) + * Adapter = all adapter calls + token queries to pools + * After = remaining gas + */ + calculateGasBreakdown(trace) { + const components = this.identifySwapComponents(trace); + const totalGas = parseInt(trace.gasUsed, 16); + + // Collect all adapter selectors + const adapterSelectors = []; + for (const methodConfig of Object.values(config.methods)) { + adapterSelectors.push(...methodConfig.adapterSelectors); + } + + // Find pool addresses from adapter calls + const poolAddresses = new Set(); + components.adapters.forEach(adapter => { + if (adapter.to) { + poolAddresses.add(adapter.to.toLowerCase()); + } + }); + + // Calculate beforeSwap, adapterGas, and afterSwap + let beforeSwap = 0; + let adapterGas = 0; + let foundFirstAdapter = false; + let ethTransfersBeforeSwap = 0; // Count ETH transfers before adapter + + if (trace.calls && trace.calls.length > 0) { + for (const call of trace.calls) { + const callSelector = call.input?.slice(0, 10); + const callGas = parseInt(call.gasUsed, 16); + const callValue = call.value ? parseInt(call.value, 16) : 0; + + // Check if this is an ETH transfer (empty input + has value) + const isETHTransfer = (!callSelector || callSelector === '0x') && callValue > 0; + + const isAdapter = adapterSelectors.includes(callSelector); + + if (isAdapter) { + foundFirstAdapter = true; + adapterGas += callGas; + } else if (!foundFirstAdapter) { + // All gas before first adapter (including token queries) goes to beforeSwap + beforeSwap += callGas; + + // Count ETH transfers before adapter (for commission distribution) + // These show gasUsed=0 but actually consume ~21000 gas each + if (isETHTransfer && callGas === 0) { + ethTransfersBeforeSwap++; + } + } + // Gas after adapters is calculated as remainder + } + } + + // Estimate gas for ETH transfers that showed 0 in trace + // Each ETH transfer to EOA or simple contract costs ~21000 gas base + some overhead + // From data: 2 transfers cost ~26653 gas total + const estimatedETHTransferGas = ethTransfersBeforeSwap > 0 + ? ethTransfersBeforeSwap * 13327 // ~13327 gas per transfer (26653 / 2) + : 0; + + beforeSwap += estimatedETHTransferGas; + + // Calculate afterSwap as total - before - adapter + const afterSwap = totalGas - beforeSwap - adapterGas; + + return { + total: trace.gasUsed, + totalDecimal: totalGas, + beforeSwap: beforeSwap, + adapters: components.adapters, + totalAdapterGas: adapterGas, + afterSwap: afterSwap, + mainMethod: components.mainMethod + }; + } + + /** + * Print gas breakdown + */ + printGasBreakdown(breakdown) { + console.log('\n=== Gas Breakdown ==='); + console.log(`Method: ${breakdown.mainMethod?.name || 'Unknown'}`); + console.log(`Total Gas: ${breakdown.total}`); + console.log(); + console.log(`Before Swap: ~${breakdown.beforeSwap.toLocaleString()}`); + console.log(); + console.log('Adapters/Pools:'); + + if (breakdown.adapters.length === 0) { + console.log(' (No adapters detected)'); + } else { + breakdown.adapters.forEach((adapter, i) => { + console.log(` ${i + 1}. ${adapter.name}: ${adapter.gasUsed.toLocaleString()}`); + }); + } + + console.log(`Total Adapter Gas: ${breakdown.totalAdapterGas.toLocaleString()}`); + console.log(); + console.log(`After Swap: ~${breakdown.afterSwap.toLocaleString()}`); + console.log('====================\n'); + } +} + +module.exports = GasTracer; + diff --git a/gasAnalyzer/analyzer/index.js b/gasAnalyzer/analyzer/index.js new file mode 100644 index 0000000..011f264 --- /dev/null +++ b/gasAnalyzer/analyzer/index.js @@ -0,0 +1,10 @@ +/** + * Analyzer index - exports all analyzer modules + */ +const GasTracer = require('./gasTracer'); +const DynamicGasAnalyzer = require('./dynamicGasAnalyzer'); + +module.exports = { + GasTracer, + DynamicGasAnalyzer +}; diff --git a/gasAnalyzer/config/chains.js b/gasAnalyzer/config/chains.js new file mode 100644 index 0000000..6e02c14 --- /dev/null +++ b/gasAnalyzer/config/chains.js @@ -0,0 +1,66 @@ +// Multi-chain configuration for DEX Router gas analysis +module.exports = { + chains: { + eth: { + name: 'Ethereum Mainnet', + chainId: 1, + rpcUrl: 'https://docs-demo.quiknode.pro/', + contract: '0x5E1f62Dac767b0491e3CE72469C217365D5B48cC', + explorer: 'https://etherscan.io' + }, + arb: { + name: 'Arbitrum One', + chainId: 42161, + rpcUrl: 'https://docs-demo.arbitrum-mainnet.quiknode.pro/', + contract: '0x368E01160C2244B0363a35B3fF0A971E44a89284', + explorer: 'https://arbiscan.io' + }, + base: { + name: 'Base', + chainId: 8453, + rpcUrl: 'https://docs-demo.base-mainnet.quiknode.pro/', + contract: '0x4409921ae43a39a11d90f7b7f96cfd0b8093d9fc', + explorer: 'https://basescan.org' + }, + bsc: { + name: 'BNB Smart Chain', + chainId: 56, + rpcUrl: 'https://docs-demo.bsc-mainnet.quiknode.pro/', + contract: '0x3156020dfF8D99af1dDC523ebDfb1ad2018554a0', + explorer: 'https://bscscan.com' + } + }, + + // Swap method selectors and adapter selectors + methods: { + dagSwap: { + selector: '0xf2c42696', + name: 'dagSwapByOrderId', + // sellBase: 0x30e6ae31, sellQuote: 0x6f7929f2 (same for both UniV2 and UniV3 adapters) + adapterSelectors: ['0x30e6ae31', '0x6f7929f2'] + }, + unxSwap: { + selector: '0x9871efa4', + name: 'unxswapByOrderId', + adapterSelectors: ['0x022c0d9f'] + }, + uniswapV3: { + selector: '0x0d5f0e3b', + name: 'uniswapV3SwapTo', + adapterSelectors: ['0x128acb08'] + } + }, + + // Example calldata for each chain and method + exampleCalldata: { + arb: { + dagSwap: '0xf2c42696000000000000000000000000000000000000000000000000003a5fb5ff172280000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000303798f000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006f308f9a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000160000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb900000000000000000000000000000000000000000000000000000000000000010000000000000000000000004df8e917de87c0de5bff92b73a3664412bb3ae770000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fc43aaf89a71acaa644842ee4219e8eb776574270000000000000000000000000000000000000000000000000000000000000001000000000000000000012710fc43aaf89a71acaa644842ee4219e8eb77657427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + unxSwap: '0x9871efa4000000000000000001af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001c0000000000000003b6d0340f64dfe17c8b87f012fcf50fbda1d62bfa148366a', + uniswapV3: '0x0d5f0e3b00000000003a5a63090a2180a5d0726690639682e48d35c0e303b3e60161dbe1000000000000000000000000000000000000000000000000000000000dc8f2ee0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004f122edcd91af8cda38c3a87158afa8687bab57c' + }, + eth: { + dagSwap: '0xf2c42696000000000000000000000000000000000000000000000000003a5fb5ff172280000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000303798f000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006f308f9a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000160000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb900000000000000000000000000000000000000000000000000000000000000010000000000000000000000004df8e917de87c0de5bff92b73a3664412bb3ae770000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fc43aaf89a71acaa644842ee4219e8eb776574270000000000000000000000000000000000000000000000000000000000000001000000000000000000012710fc43aaf89a71acaa644842ee4219e8eb77657427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + } + } +}; + diff --git a/gasAnalyzer/config/index.js b/gasAnalyzer/config/index.js new file mode 100644 index 0000000..ec15dda --- /dev/null +++ b/gasAnalyzer/config/index.js @@ -0,0 +1,11 @@ +/** + * Configuration index - exports all config modules + */ +const chains = require('./chains'); +const pools = require('./pools'); + +module.exports = { + ...chains, + ...pools +}; + diff --git a/gasAnalyzer/config/pools.js b/gasAnalyzer/config/pools.js new file mode 100644 index 0000000..8d1e086 --- /dev/null +++ b/gasAnalyzer/config/pools.js @@ -0,0 +1,212 @@ +/** + * Pool Configuration for each chain + * Defines WETH-USDC pools for UniswapV2 and UniswapV3 + */ +module.exports = { + pools: { + // Arbitrum One + arb: { + weth: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + usdc: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + + // UniswapV3 Pool (WETH-USDC 0.05% fee) + uniswapV3: { + pool: '0xC6962004f452bE9203591991D15f6b388e09E8D0', + fee: 500, + token0: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', // WETH + token1: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC + }, + + // UniswapV2 / UNX Pool (WETH-USDC) + uniswapV2: { + pool: '0xF64Dfe17C8b87F012FCf50FbDA1D62bfA148366a', + token0: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', // WETH + token1: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC + }, + + // DagSwap Adapter - supports both UniswapV2 and UniswapV3 pools + // token0/token1 fetched dynamically from pool contract + // Use POOL_TYPE env var or options.poolType to select: 'uniV2' | 'uniV3' + dagSwap: { + uniV2: { + adapter: '0x808ca026D4c45d6A41c1e807c41044480b7699eF', + pool: '0xF64Dfe17C8b87F012FCf50FbDA1D62bfA148366a', + }, + uniV3: { + adapter: '0x6747BcaF9bD5a5F0758Cbe08903490E45DdfACB5', + pool: '0xC6962004f452bE9203591991D15f6b388e09E8D0', // WETH-USDC 0.05% + // sqrtPriceX96: set to 0 to use default (MIN/MAX based on direction) + sqrtPriceX96: '0', + }, + }, + + // Test amounts + amounts: { + 'ERC20->ERC20': { + // 0.01 USDC (10000 wei, 6 decimals) + fromAmount: '10000', + fromToken: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC + toToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', // WETH + minReturn: '1' + }, + 'ETH->ERC20': { + // 0.000001 ETH (1000000000000 wei) + fromAmount: '1000000000000', + fromToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // ETH placeholder + toToken: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC + minReturn: '1' + }, + 'ERC20->ETH': { + // 0.01 USDC (10000 wei) + fromAmount: '10000', + fromToken: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC + toToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // ETH placeholder + minReturn: '1' + } + } + }, + + // Ethereum Mainnet + eth: { + weth: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + + uniswapV3: { + pool: '0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640', // USDC-WETH 0.05% + fee: 500, + token0: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC + token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH + }, + + uniswapV2: { + pool: '0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc', // USDC-WETH + token0: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC + token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH + }, + + dagSwap: { + uniV2: { + adapter: '0xc837BbEa8C7b0caC0e8928f797ceB04A34c9c06e', + pool: '0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc', + }, + uniV3: { + adapter: '0x6747BcaF9bD5a5F0758Cbe08903490E45DdfACB5', + pool: '0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640', // WETH-USDC 0.05% + // sqrtPriceX96: set to 0 to use default (MIN/MAX based on direction) + sqrtPriceX96: '0', + }, + }, + + amounts: { + 'ERC20->ERC20': { + fromAmount: '10000', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC + toToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH + minReturn: '1' + }, + 'ETH->ERC20': { + fromAmount: '1000000000000', + fromToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + toToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + minReturn: '1' + }, + 'ERC20->ETH': { + fromAmount: '10000', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + minReturn: '1' + } + } + }, + + // Base + base: { + weth: '0x4200000000000000000000000000000000000006', + usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + + uniswapV3: { + pool: '0xd0b53D9277642d899DF5C87A3966A349A798F224', // WETH-USDC 0.05% + fee: 500, + token0: '0x4200000000000000000000000000000000000006', // WETH + token1: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC + }, + + uniswapV2: { + pool: '0x88A43bbDF9D098eEC7bCEda4e2494615dfD9bB9C', // Estimated + token0: '0x4200000000000000000000000000000000000006', // WETH + token1: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC + }, + + amounts: { + 'ERC20->ERC20': { + fromAmount: '10000', + fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + toToken: '0x4200000000000000000000000000000000000006', + minReturn: '1' + }, + 'ETH->ERC20': { + fromAmount: '1000000000000', + fromToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + toToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + minReturn: '1' + }, + 'ERC20->ETH': { + fromAmount: '10000', + fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + toToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + minReturn: '1' + } + } + }, + + // BSC + bsc: { + weth: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB + usdc: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', + + uniswapV3: { + pool: '0x36696169C63e42cd08ce11f5deeBbCeBae652050', // Estimated + fee: 500, + token0: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC + token1: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB + }, + + uniswapV2: { + pool: '0xd99c7F6C65857AC913a8f880A4cb84032AB2FC5b', // PancakeSwap + token0: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC + token1: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB + }, + + amounts: { + 'ERC20->ERC20': { + fromAmount: '10000', + fromToken: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', + toToken: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', + minReturn: '1' + }, + 'ETH->ERC20': { + fromAmount: '1000000000000', + fromToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + toToken: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', + minReturn: '1' + }, + 'ERC20->ETH': { + fromAmount: '10000', + fromToken: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', + toToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + minReturn: '1' + } + } + } + }, + + // Commission recipient addresses (for testing) + commissionRecipients: { + recipient1: '0x399efa78cacd7784751cd9fbf2523edf9efdf6ad', + recipient2: '0x591342772bbc7d0630efbdea3c0b704e7addad17' + }, + + // Default from address for transactions + defaultFrom: '0x358506b4c5c441873ade429c5a2be777578e2c6f' +}; + diff --git a/gasAnalyzer/encoder/calldataEncoder.js b/gasAnalyzer/encoder/calldataEncoder.js new file mode 100644 index 0000000..1919a24 --- /dev/null +++ b/gasAnalyzer/encoder/calldataEncoder.js @@ -0,0 +1,571 @@ +const { ethers } = require('ethers'); +const poolConfig = require('../config/pools'); +const { fetchUniV2PoolInfo, fetchUniV3PoolInfo } = require('../utils/fetchPoolInfo'); + +/** + * Calldata Encoder - Generates base calldata for all swap methods + * Dynamically creates calldata based on pool configurations + * + * Pool token0/token1 are fetched from chain if not provided in config + */ +class CalldataEncoder { + // Cache for fetched pool info to avoid repeated RPC calls + static _poolInfoCache = {}; + + /** + * Get pool info, either from config or fetched from chain + */ + static async _getPoolInfo(chain, poolAddress, poolType) { + const cacheKey = `${chain}:${poolAddress}`; + + if (this._poolInfoCache[cacheKey]) { + return this._poolInfoCache[cacheKey]; + } + + console.log(`Fetching pool info from chain: ${poolAddress}`); + + let info; + if (poolType === 'uniswapV3') { + info = await fetchUniV3PoolInfo(chain, poolAddress); + } else { + info = await fetchUniV2PoolInfo(chain, poolAddress); + } + + this._poolInfoCache[cacheKey] = info; + return info; + } + /** + * Encode uniswapV3SwapTo calldata + */ + static encodeUniswapV3SwapTo(chain, swapType, blockNumber = 'latest') { + const config = poolConfig.pools[chain]; + + if (!config) { + throw new Error(`Chain ${chain} not found in poolConfig.pools`); + } + + if (!config.amounts) { + throw new Error(`No amounts configured for chain ${chain}`); + } + + const amounts = config.amounts[swapType]; + + if (!amounts) { + throw new Error(`No amounts configured for swapType ${swapType} on chain ${chain}. Available: ${Object.keys(config.amounts).join(', ')}`); + } + + const pool = config.uniswapV3; + + // Determine swap direction + const isUSDCtoWETH = swapType === 'ERC20->ERC20' || swapType === 'ERC20->ETH'; + const isWETHtoUSDC = swapType === 'ETH->ERC20'; + + // For uniswapV3, isOneForZero determines swap direction + // isOneForZero = true means swapping token1 -> token0 + // isOneForZero = false means swapping token0 -> token1 + let isOneForZero; + + if (swapType === 'ERC20->ERC20' || swapType === 'ERC20->ETH') { + // USDC -> WETH + // If USDC is token1, we're swapping token1 -> token0, so isOneForZero = true + isOneForZero = pool.token1.toLowerCase() === config.usdc.toLowerCase(); + } else { + // WETH -> USDC + // If WETH is token1, we're swapping token1 -> token0, so isOneForZero = true + isOneForZero = pool.token1.toLowerCase() === config.weth.toLowerCase(); + } + + // Encode receiver as packed uint256 + // The receiver has a flag in the upper bits and address in lower bits + // Based on actual calldata: 0x000000000000000000000001 + address + // This means bit 160 is set (the bit just above the address space) + let receiverPacked = BigInt(poolConfig.defaultFrom); + receiverPacked |= (BigInt(1) << BigInt(160)); // Set bit 160 + + // Encode pool as packed uint256 + let poolPacked = BigInt(pool.pool); + if (isOneForZero) { + poolPacked |= (BigInt(1) << BigInt(255)); // Set _ONE_FOR_ZERO_MASK (bit 255) + } + + // For ERC20->ETH, set WETH unwrap flag so output is ETH instead of WETH + if (swapType === 'ERC20->ETH') { + poolPacked |= (BigInt(1) << BigInt(253)); // Set _WETH_UNWRAP_MASK (bit 253) + } + + // Function selector + const selector = '0x0d5f0e3b'; + + // ABI encode parameters + // NOTE: ethers v5 uses ethers.utils.defaultAbiCoder (v6 uses ethers.AbiCoder.defaultAbiCoder()). + const abiCoder = ethers.utils.defaultAbiCoder; + + // Parameters: receiver (uint256), amount (uint256), minReturn (uint256), pools (uint256[]) + const params = abiCoder.encode( + ['uint256', 'uint256', 'uint256', 'uint256[]'], + [ + '0x' + receiverPacked.toString(16).padStart(64, '0'), + amounts.fromAmount, + amounts.minReturn, + ['0x' + poolPacked.toString(16).padStart(64, '0')] + ] + ); + + const calldata = selector + params.slice(2); + + return { + calldata, + from: poolConfig.defaultFrom, + to: null, // Will be set by chain config + value: swapType === 'ETH->ERC20' ? '0x' + BigInt(amounts.fromAmount).toString(16) : '0x0', + blockNumber, + metadata: { + method: 'uniswapV3SwapTo', + swapType, + pool: pool.pool, + fromToken: amounts.fromToken, + toToken: amounts.toToken, + amount: amounts.fromAmount, + isOneForZero + } + }; + } + + /** + * Encode unxswapByOrderId calldata (uses UniswapV2-style pools) + */ + static encodeUnxswapByOrderId(chain, swapType, blockNumber = 'latest') { + const config = poolConfig.pools[chain]; + const amounts = config.amounts[swapType]; + const pool = config.uniswapV2; + + // srcToken encoding (packed uint256) + // Upper bits (160-255): Order ID + // Lower bits (0-159): fromToken address + // Special case: For ETH, use address(0) instead of 0xEeee... + // Contract validation: (srcTokenAddr == fromToken) || (srcTokenAddr == address(0) && fromToken == _ETH) + + // Determine the srcToken address + let srcTokenAddress; + if (amounts.fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') { + // For ETH, use address(0) as per contract logic + srcTokenAddress = BigInt(0); + } else { + // For ERC20 tokens, use actual token address + srcTokenAddress = BigInt(amounts.fromToken); + } + + // Add order ID in upper bits (using 0x01 as default order ID) + const orderId = BigInt(1); + let srcToken = srcTokenAddress | (orderId << BigInt(160)); + + // Determine swap direction and flags + // Pool: token0=WETH, token1=USDC + // + // Path format (from working calldata): + // byte 0: flags (c0 = REVERSE+WETH, 80 = REVERSE only, 00 = no flags) + // bytes 1-11: padding (000000000000) + // bytes 12-15: fee info (003b6d03) + // byte 16: marker (40) + // bytes 17-36: pool address (20 bytes) + // + // This is NOT a simple bytes32 with bit flags! It's a custom packed format. + + const poolAddress = pool.pool.toLowerCase().replace('0x', ''); + + let flagByte; + if (swapType === 'ERC20->ETH') { + // USDC->WETH with unwrap to ETH + flagByte = 'c0'; + } else if (swapType === 'ERC20->ERC20') { + // USDC->WETH without unwrap + flagByte = '80'; + } else { + // ETH->ERC20: WETH->USDC, no special handling + flagByte = '00'; + } + + // Use the exact format from working calldata + const pathContent = flagByte + '000000000000' + '003b6d03' + '40' + poolAddress; + + // Function selector + const selector = '0x9871efa4'; + + // Manually construct calldata because path has special encoding + // Params: uint256 srcToken, uint256 amount, uint256 minReturn, bytes path + const srcTokenHex = srcToken.toString(16).padStart(64, '0'); + const amountHex = BigInt(amounts.fromAmount).toString(16).padStart(64, '0'); + const minReturnHex = BigInt(amounts.minReturn).toString(16).padStart(64, '0'); + + // Path offset = 0x80 (128 bytes = 4 params * 32 bytes) + const pathOffset = '0000000000000000000000000000000000000000000000000000000000000080'; + + // Path: length (1) + content (32 bytes) + const pathLength = '0000000000000000000000000000000000000000000000000000000000000001'; + const pathData = pathContent.padEnd(64, '0'); + + const calldata = selector + srcTokenHex + amountHex + minReturnHex + pathOffset + pathLength + pathData; + + return { + calldata, + from: poolConfig.defaultFrom, + to: null, + value: swapType === 'ETH->ERC20' ? '0x' + BigInt(amounts.fromAmount).toString(16) : '0x0', + blockNumber, + metadata: { + method: 'unxswapByOrderId', + swapType, + pool: pool.pool, + fromToken: amounts.fromToken, + toToken: amounts.toToken, + amount: amounts.fromAmount + } + }; + } + + /** + * Encode dagSwapByOrderId calldata + * Supports both UniswapV2 and UniswapV3 style pools + * + * @param {string} chain - Chain identifier + * @param {string} swapType - 'ERC20->ERC20' | 'ETH->ERC20' | 'ERC20->ETH' + * @param {string} blockNumber - Block number or 'latest' + * @param {string} poolTypeKey - 'uniV2' (default) or 'uniV3' + */ + static async encodeDagSwapByOrderId(chain, swapType, blockNumber = 'latest', poolTypeKey = 'uniV2') { + const config = poolConfig.pools[chain]; + const amounts = config.amounts[swapType]; + + if (!config.dagSwap) { + throw new Error(`No dagSwap configuration for chain ${chain}`); + } + + const dagSwap = config.dagSwap[poolTypeKey]; + if (!dagSwap) { + throw new Error(`No dagSwap.${poolTypeKey} configuration for chain ${chain}. Available: ${Object.keys(config.dagSwap).join(', ')}`); + } + + // Map poolTypeKey to poolType for internal use + const poolType = poolTypeKey === 'uniV3' ? 'uniswapV3' : 'uniswapV2'; + + // Fetch pool info from chain if token0/token1 not in config + let poolInfo = dagSwap; + if (!dagSwap.token0 || !dagSwap.token1) { + const chainPoolInfo = await this._getPoolInfo(chain, dagSwap.pool, poolType); + // Merge: config values take priority over fetched values + poolInfo = { ...chainPoolInfo, ...dagSwap }; + } + + // Determine from/to tokens based on swap type + let fromTokenAddr, toTokenAddr, routeFromToken, routeToToken; + + if (poolType === 'uniswapV3') { + // For UniV3 pools, dynamically determine which token is WETH and which is USDC + // ETH: token0=USDC, token1=WETH | ARB: token0=WETH, token1=USDC + const poolWeth = poolInfo.token0.toLowerCase() === config.weth.toLowerCase() + ? poolInfo.token0 : poolInfo.token1; + const poolUsdc = poolInfo.token0.toLowerCase() === config.usdc.toLowerCase() + ? poolInfo.token0 : poolInfo.token1; + + if (swapType === 'ERC20->ERC20') { + // USDC -> WETH + fromTokenAddr = poolUsdc; + toTokenAddr = poolWeth; + routeFromToken = poolUsdc; + routeToToken = poolWeth; + } else if (swapType === 'ETH->ERC20') { + // ETH -> USDC: wrap ETH to WETH, swap WETH -> USDC + fromTokenAddr = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + toTokenAddr = poolUsdc; // USDC (final output) + routeFromToken = poolWeth; // WETH (in pool) + routeToToken = poolUsdc; // USDC (in pool) + } else { // ERC20->ETH + // USDC -> ETH: swap USDC -> WETH, unwrap WETH to ETH + fromTokenAddr = poolUsdc; // USDC + toTokenAddr = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + routeFromToken = poolUsdc; // USDC (in pool) + routeToToken = poolWeth; // WETH (in pool) + } + } else { + // UniswapV2 style - use fetched pool token0/token1 + // token0 is typically the "lower" address, token1 the "higher" + // For WETH-USDC pools: need to determine which is which + if (swapType === 'ERC20->ERC20') { + // USDC -> WETH (assuming USDC = token1, WETH = token0 in most pools) + fromTokenAddr = config.usdc; + toTokenAddr = config.weth; + routeFromToken = config.usdc; + routeToToken = config.weth; + } else if (swapType === 'ETH->ERC20') { + fromTokenAddr = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + toTokenAddr = config.usdc; + routeFromToken = config.weth; + routeToToken = config.usdc; + } else { // ERC20->ETH + fromTokenAddr = config.usdc; + toTokenAddr = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + routeFromToken = config.usdc; + routeToToken = config.weth; + } + } + + // Convert to BigInt for RouterPath.fromToken (uint256) + const fromToken = BigInt(routeFromToken); + const toToken = toTokenAddr; + + // Deadline (current timestamp + 1 hour) + const deadline = Math.floor(Date.now() / 1000) + 3600; + + let route; + + if (poolType === 'uniswapV3') { + // UniswapV3 style encoding + route = this._encodeUniV3DagSwapRoute(poolInfo, routeFromToken, routeToToken, fromToken); + } else { + // UniswapV2 style encoding + route = this._encodeUniV2DagSwapRoute(poolInfo, config, routeFromToken, fromToken, swapType); + } + + // Function selector + const selector = '0xf2c42696'; + + const abiCoder = ethers.utils.defaultAbiCoder; + + // dagSwapByOrderId parameters: + // uint256 orderId, address fromToken, address toToken, uint256 amount, + // uint256 minReturn, uint256 deadline, RouterPath[] routes + const params = abiCoder.encode( + ['uint256', 'address', 'address', 'uint256', 'uint256', 'uint256', 'tuple(address[],address[],uint256[],bytes[],uint256)[]'], + [ + 1, // orderId + fromTokenAddr, + toToken, + amounts.fromAmount, + amounts.minReturn, + deadline, + [[ + route.mixAdapters, + route.assetTo, + route.rawData, + route.extraData, + route.fromToken + ]] + ] + ); + + const calldata = selector + params.slice(2); + + return { + calldata, + from: poolConfig.defaultFrom, + to: null, + value: swapType === 'ETH->ERC20' ? '0x' + BigInt(amounts.fromAmount).toString(16) : '0x0', + blockNumber, + metadata: { + method: 'dagSwapByOrderId', + swapType, + poolType, + adapter: dagSwap.adapter, + pool: dagSwap.pool, + fromToken: fromTokenAddr, + toToken: toTokenAddr, + amount: amounts.fromAmount, + poolInfo: { + token0: poolInfo.token0, + token1: poolInfo.token1, + ...(poolType === 'uniswapV3' ? { fee: poolInfo.fee } : {}) + } + } + }; + } + + /** + * Encode UniswapV2 style dagSwap route + * + * @param {Object} poolInfo - Pool info with token0, token1 (fetched from chain if needed) + * @param {Object} config - Chain pool config + * @param {string} routeFromToken - From token address + * @param {BigInt} fromToken - From token as BigInt + * @param {string} swapType - Swap type + */ + static _encodeUniV2DagSwapRoute(poolInfo, config, routeFromToken, fromToken, swapType) { + // Determine swap direction (reverse flag) + // For UniswapV2-style pools: reverse = true if swapping token1 -> token0 + // If routeFromToken == token1, we're doing token1 -> token0 (reverse) + const isReverse = poolInfo.token1 && + routeFromToken.toLowerCase() === poolInfo.token1.toLowerCase(); + + // Pack pool parameter with flags + let poolPacked = BigInt(poolInfo.pool); + + if (isReverse) { + poolPacked |= (BigInt(1) << BigInt(255)); // Set REVERSE_MASK + } + + // Set weight to 10000 (0x2710) at bits 160-175 + poolPacked |= (BigInt(0x2710) << BigInt(160)); + + // Set inputIndex (0) at bits 184-191 + poolPacked |= (BigInt(0x00) << BigInt(184)); + + // Set outputIndex (1) at bits 176-183 + poolPacked |= (BigInt(0x01) << BigInt(176)); + + return { + mixAdapters: [poolInfo.adapter], + assetTo: [poolInfo.pool], + rawData: ['0x' + poolPacked.toString(16).padStart(64, '0')], + extraData: ['0x00'], + fromToken: '0x' + fromToken.toString(16).padStart(64, '0') + }; + } + + /** + * Encode UniswapV3 style dagSwap route + * + * Key differences from UniV2: + * 1. assetTo = adapter address (not pool address) + * 2. extraData = abi.encode(sqrtX96, abi.encode(fromToken, toToken)) + * 3. rawData still packs pool address with REVERSE flag + * + * @param {Object} poolInfo - Pool info with token0, token1, fee (fetched from chain) + */ + static _encodeUniV3DagSwapRoute(poolInfo, routeFromToken, routeToToken, fromToken) { + const abiCoder = ethers.utils.defaultAbiCoder; + + // Determine swap direction for UniV3 + // zeroForOne = fromToken < toToken (comparing addresses) + const fromTokenLower = routeFromToken.toLowerCase(); + const toTokenLower = routeToToken.toLowerCase(); + const zeroForOne = fromTokenLower < toTokenLower; + + // Pack pool parameter with flags + // For UniV3: REVERSE = !zeroForOne (i.e., token1 -> token0) + let poolPacked = BigInt(poolInfo.pool); + + if (!zeroForOne) { + // If swapping token1 -> token0, set REVERSE flag + poolPacked |= (BigInt(1) << BigInt(255)); + } + + // Set weight to 10000 (0x2710) at bits 160-175 + poolPacked |= (BigInt(0x2710) << BigInt(160)); + + // Set inputIndex (0) at bits 184-191 + poolPacked |= (BigInt(0x00) << BigInt(184)); + + // Set outputIndex (1) at bits 176-183 + poolPacked |= (BigInt(0x01) << BigInt(176)); + + // Encode extraData for UniV3 + // From BaseUniversalUniswapV3Adapter._sell(): + // (uint160 sqrtX96, bytes memory data) = abi.decode(moreInfo, (uint160, bytes)) + // (address fromToken, address toToken) = abi.decode(data, (address, address)) + const sqrtX96 = poolInfo.sqrtPriceX96 || '0'; + const tokenData = abiCoder.encode(['address', 'address'], [routeFromToken, routeToToken]); + const extraData = abiCoder.encode(['uint160', 'bytes'], [sqrtX96, tokenData]); + + return { + mixAdapters: [poolInfo.adapter], + assetTo: [poolInfo.adapter], // For UniV3, assetTo = adapter + rawData: ['0x' + poolPacked.toString(16).padStart(64, '0')], + extraData: [extraData], + fromToken: '0x' + fromToken.toString(16).padStart(64, '0') + }; + } + + /** + * Generate calldata for specific method, chain, and swap type + * + * @param {string} method - 'uniswapV3' | 'unxSwap' | 'dagSwap' + * @param {string} chain - Chain identifier + * @param {string} swapType - 'ERC20->ERC20' | 'ETH->ERC20' | 'ERC20->ETH' + * @param {string} blockNumber - Block number or 'latest' + * @param {Object} options - Optional settings + * @param {string} options.poolType - 'uniV2' | 'uniV3' (for dagSwap, default: use env POOL_TYPE or 'uniV2') + */ + static async generate(method, chain, swapType, blockNumber = 'latest', options = {}) { + if (!poolConfig.pools[chain]) { + throw new Error(`Chain ${chain} not configured in poolConfig`); + } + + switch (method) { + case 'uniswapV3': + return this.encodeUniswapV3SwapTo(chain, swapType, blockNumber); + case 'unxSwap': + return this.encodeUnxswapByOrderId(chain, swapType, blockNumber); + case 'dagSwap': + // Determine pool type based on options or environment variable + const poolTypeKey = options.poolType || process.env.POOL_TYPE || 'uniV2'; + return await this.encodeDagSwapByOrderId(chain, swapType, blockNumber, poolTypeKey); + default: + throw new Error(`Unknown method: ${method}. Available: uniswapV3, unxSwap, dagSwap`); + } + } + + /** + * Generate all swap types for a method and chain + */ + static async generateAllSwapTypes(method, chain, blockNumber = 'latest') { + const swapTypes = ['ERC20->ERC20', 'ETH->ERC20', 'ERC20->ETH']; + const results = {}; + + for (const swapType of swapTypes) { + results[swapType] = await this.generate(method, chain, swapType, blockNumber); + } + + return results; + } + + /** + * Clear the pool info cache + */ + static clearCache() { + this._poolInfoCache = {}; + } +} + +// CLI interface +if (require.main === module) { + (async () => { + console.log('=== Calldata Encoder Test ===\n'); + + const chain = process.argv[2] || 'arb'; + const method = process.argv[3] || 'dagSwap'; + const swapType = process.argv[4] || 'ERC20->ERC20'; + + console.log(`Chain: ${chain}`); + console.log(`Method: ${method}`); + console.log(`Swap Type: ${swapType}\n`); + + try { + const result = await CalldataEncoder.generate(method, chain, swapType); + + console.log('Generated Calldata:'); + console.log(' Calldata:', result.calldata.slice(0, 66) + '...'); + console.log(' Full Length:', result.calldata.length, 'chars'); + console.log(' From:', result.from); + console.log(' Value:', result.value); + console.log('\nMetadata:'); + console.log(JSON.stringify(result.metadata, null, 2)); + + // If dagSwap, show poolInfo + if (result.metadata.poolInfo) { + console.log('\nPool Info (fetched from chain):'); + console.log(' token0:', result.metadata.poolInfo.token0); + console.log(' token1:', result.metadata.poolInfo.token1); + if (result.metadata.poolInfo.fee) { + console.log(' fee:', result.metadata.poolInfo.fee); + } + } + + } catch (error) { + console.error('Error:', error.message); + console.error(error.stack); + process.exit(1); + } + })(); +} + +module.exports = CalldataEncoder; + diff --git a/gasAnalyzer/encoder/index.js b/gasAnalyzer/encoder/index.js new file mode 100644 index 0000000..00ed34a --- /dev/null +++ b/gasAnalyzer/encoder/index.js @@ -0,0 +1,10 @@ +/** + * Encoder index - exports all encoder modules + */ +const CalldataEncoder = require('./calldataEncoder'); +const ScenarioBuilder = require('./scenarioBuilder'); + +module.exports = { + CalldataEncoder, + ScenarioBuilder +}; diff --git a/gasAnalyzer/encoder/scenarioBuilder.js b/gasAnalyzer/encoder/scenarioBuilder.js new file mode 100644 index 0000000..eb24b0e --- /dev/null +++ b/gasAnalyzer/encoder/scenarioBuilder.js @@ -0,0 +1,327 @@ +const config = require('../config/chains'); + +/** + * Scenario Builder - Generate specific calldata suffix for different test scenarios + * Based on eth-gas-measurement project scenario design + */ +class ScenarioBuilder { + + /** + * Extract fromToken and toToken addresses from calldata + * For ETH-related scenarios, infer correct token address from swapType + * @param {string} calldata - The base calldata + * @param {string} swapType - Swap type + * @param {string} chain - Chain identifier (default: 'arb') + */ + static extractTokenAddresses(calldata, swapType, chain = 'arb') { + const poolConfig = require('../config/pools'); + const poolCfg = poolConfig.pools[chain] || poolConfig.pools.arb; + const ETH_ADDRESS = 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; + + // For ETH-related scenarios, return correct token based on swapType + // This ensures commission/trim uses correct token address + if (swapType === 'ETH->ERC20') { + // ETH -> USDC: fromToken=ETH, toToken=USDC + return { + fromToken: ETH_ADDRESS, + toToken: poolCfg.usdc.toLowerCase().replace('0x', '') + }; + } else if (swapType === 'ERC20->ETH') { + // USDC -> ETH: fromToken=USDC, toToken=ETH (unwrapped) + return { + fromToken: poolCfg.usdc.toLowerCase().replace('0x', ''), + toToken: ETH_ADDRESS + }; + } + + // For ERC20->ERC20, extract from calldata or use default + const selector = calldata.slice(0, 10); + + if (selector === '0xf2c42696') { + // dagSwapByOrderId: extract from calldata + const hex = calldata.startsWith('0x') ? calldata.slice(2) : calldata; + const param2 = hex.slice(72, 136); + const param3 = hex.slice(136, 200); + const fromToken = param2.slice(24); + const toToken = param3.slice(24); + return { fromToken, toToken }; + } + + // Default: USDC -> WETH + return { + fromToken: poolCfg.usdc.toLowerCase().replace('0x', ''), + toToken: poolCfg.weth.toLowerCase().replace('0x', '') + }; + } + + /** + * Generate fromToken single commission suffix + * SINGLE mode: 1 referrer with flag 3ca2 + * Contract reads backwards: + * - calldatasize() - 0x20: Entry 0 (flag + rate + recipient) + * - calldatasize() - 0x40: Token (flag + token address) + * + * Append order (left to right): + * - Token (32 bytes): flag + token address + * - Entry 0 (32 bytes): flag + rate + recipient + */ + static generateFromTokenSingleCommission(fromToken) { + // Single commission: 1 referrer entry with rate 10000000 + return '800000000000000000000000' + fromToken + // Token (first) + '3ca20afc2aaa000000989680399efa78cacd7784751cd9fbf2523edf9efdf6ad'; // Entry 0 (last) + } + + /** + * Generate fromToken double commission suffix + * DUAL mode: 2 referrers with flag 2222 + * Contract reads backwards: + * - calldatasize() - 0x20: Entry 0 (flag + rate + recipient) + * - calldatasize() - 0x40: Token (flag + token address) + * - calldatasize() - 0x60: Entry 1 (flag + rate + recipient) + * + * Structure (reading from end backwards): + * - Entry 1 (32 bytes): flag + rate + recipient + * - Token (32 bytes): flag + token address <- in the middle! + * - Entry 0 (32 bytes): flag + rate + recipient <- last + */ + static generateFromTokenDoubleCommission(fromToken) { + // Dual commission: 2 referrer entries with rate 10000000 each + return '22220afc2aaa000000989680591342772bbc7d0630efbdea3c0b704e7addad17' + // Entry 1 + '800000000000000000000000' + fromToken + // Token (middle) + '22220afc2aaa000000989680399efa78cacd7784751cd9fbf2523edf9efdf6ad'; // Entry 0 (last) + } + + /** + * Generate toToken single commission suffix + * Increases gas in afterSwap phase + */ + static generateToTokenSingleCommission(toToken) { + return '800000000000000000000000' + toToken + '3ca20afc2bbb000000989680399efa78cacd7784751cd9fbf2523edf9efdf6ad'; + } + + /** + * Generate toToken double commission suffix + */ + static generateToTokenDoubleCommission(toToken) { + return '22220afc2bbb000000989680399efa78cacd7784751cd9fbf2523edf9efdf6ad' + + '800000000000000000000000' + toToken + + '22220afc2bbb000000989680358506b4c5c441873ade429c5a2be777578e2c6f'; + } + + /** + * Generate trim single address suffix (TRIM_FLAG = 0x777777771111) + * trimRate = 10 (1% of TRIM_DENOMINATOR=1000), must be <= TRIM_RATE_LIMIT=100 + */ + static generateTrimSingle(toToken) { + // trimRate = 0x0a = 10 (1%) + // expectAmountOut = 1 + // trimAddress = 0x399efa78cacd7784751cd9fbf2523edf9efdf6ad + return '7777777711110000000000000000000000000000000000000000000000000001' + // Entry 2: expectAmountOut = 1 + '77777777111100000000000a399efa78cacd7784751cd9fbf2523edf9efdf6ad'; // Entry 1: flag + rate=10 + address + } + + /** + * Generate trim double address suffix (TRIM_DUAL_FLAG = 0x777777772222) + * trimRate = 10 (1%), chargeRate can be any value (for distributing trim amount) + */ + static generateTrimDouble(toToken) { + // trimRate = 0x0a = 10 (1%), chargeRate = 0x1f4 = 500 (50% to charge address) + // expectAmountOut = 1 + return '7777777722220000000001f4591342772bbc7d0630efbdea3c0b704e7addad17' + // Entry 3: chargeRate=500 + chargeAddr + '7777777722220000000000000000000000000000000000000000000000000001' + // Entry 2: expectAmountOut = 1 + '77777777222200000000000a399efa78cacd7784751cd9fbf2523edf9efdf6ad'; // Entry 1: flag + rate=10 + trimAddr + } + + /** + * Generate toToken double commission + trim double address (max gas) suffix + */ + static generateMaxGasScenario(fromToken, toToken, swapType) { + // TRIM double address (append first, before commission) + // trimRate = 10 (1%), chargeRate = 500 (50%) + const trimEntries = + '7777777722220000000001f4591342772bbc7d0630efbdea3c0b704e7addad17' + // Entry 3: chargeRate=500 + chargeAddr + '7777777722220000000000000000000000000000000000000000000000000001' + // Entry 2: expectAmountOut = 1 + '77777777222200000000000a399efa78cacd7784751cd9fbf2523edf9efdf6ad'; // Entry 1: flag + rate=10 + trimAddr + + // toToken double commission + return trimEntries + + '22220afc2bbb000000989680399efa78cacd7784751cd9fbf2523edf9efdf6ad' + // Entry 1: referrer2 + '800000000000000000000000' + toToken + // Token + '22220afc2bbb000000989680591342772bbc7d0630efbdea3c0b704e7addad17'; // Entry 0: referrer1 + } + + /** + * Get calldata suffix for scenario name + * @param {string} scenarioName - Scenario name + * @param {string} baseCalldata - Base calldata + * @param {string} swapType - Swap type + * @param {string} chain - Chain identifier (default: 'arb') + */ + static getSuffixForScenario(scenarioName, baseCalldata, swapType, chain = 'arb') { + const { fromToken, toToken } = ScenarioBuilder.extractTokenAddresses(baseCalldata, swapType, chain); + + const suffixMap = { + 'basic': '', + 'fromToken_single_commission': ScenarioBuilder.generateFromTokenSingleCommission(fromToken), + 'fromToken_double_commission': ScenarioBuilder.generateFromTokenDoubleCommission(fromToken), + 'toToken_single_commission': ScenarioBuilder.generateToTokenSingleCommission(toToken), + 'toToken_double_commission': ScenarioBuilder.generateToTokenDoubleCommission(toToken), + 'trim_single': ScenarioBuilder.generateTrimSingle(toToken), + 'trim_double': ScenarioBuilder.generateTrimDouble(toToken), + 'max_gas_scenario': ScenarioBuilder.generateMaxGasScenario(fromToken, toToken, swapType) + }; + + return suffixMap[scenarioName] || ''; + } + + /** + * Build complete calldata for scenario + * @param {string} baseCalldata - Base calldata + * @param {string} scenarioName - Scenario name + * @param {string} swapType - Swap type + * @param {string} chain - Chain identifier (default: 'arb') + */ + static buildCalldata(baseCalldata, scenarioName, swapType, chain = 'arb') { + const suffix = ScenarioBuilder.getSuffixForScenario(scenarioName, baseCalldata, swapType, chain); + return baseCalldata + suffix; + } + + /** + * Calculate extra ETH value needed for scenario (only for ETH->ERC20 with fromToken commission) + * @param {string} scenarioName - Scenario name + * @param {string} swapType - Swap type + * @param {string} swapAmount - Swap amount (hex string with 0x prefix) + * @returns {string} Extra ETH amount needed (hex string) + */ + static calculateExtraValue(scenarioName, swapType, swapAmount) { + // Only ETH->ERC20 with fromToken commission needs extra value + if (swapType !== 'ETH->ERC20') { + return '0x0'; + } + + // max_gas_scenario now uses toToken commission, no extra ETH needed + if (scenarioName === 'max_gas_scenario') { + return '0x0'; + } + + // Commission calculation formula (extracted from contract code): + // commission = (inputAmount * rate) / (DENOMINATOR - totalRate) + // where: + // - rate = 0x989680 = 10,000,000 (per commission entry) + // - DENOMINATOR = 10^9 = 1,000,000,000 + // - totalRate = rate * numEntries + const inputAmount = BigInt(swapAmount); + const RATE_PER_ENTRY = BigInt(10000000); // 0x989680 + const DENOMINATOR = BigInt(1000000000); // 10^9 + + let numEntries = 0; + if (scenarioName === 'fromToken_single_commission') { + numEntries = 2; // Single has 2 commission entries + } else if (scenarioName === 'fromToken_double_commission') { + numEntries = 4; // Double has 4 commission entries + } + + if (numEntries === 0) { + return '0x0'; + } + + const totalRate = RATE_PER_ENTRY * BigInt(numEntries); + const totalCommission = (inputAmount * totalRate) / (DENOMINATOR - totalRate); + + return '0x' + totalCommission.toString(16); + } + + /** + * Generate all scenarios calldata + * @param {string} baseCalldata - Base calldata + * @param {string} swapType - Swap type + * @param {string} swapAmount - Swap amount (hex string with 0x prefix), for calculating ETH->ERC20 extra value + * @param {string} chain - Chain identifier (default: 'arb') + */ + static generateAllScenarios(baseCalldata, swapType = 'ERC20->ETH', swapAmount = '0x0', chain = 'arb') { + const scenarios = [ + { + name: 'basic', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'basic', swapType, chain), + description: `Basic ${swapType} swap`, + extraValue: '0x0' + }, + { + name: 'fromToken_single_commission', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'fromToken_single_commission', swapType, chain), + description: `${swapType} with single fromToken commission`, + extraValue: ScenarioBuilder.calculateExtraValue('fromToken_single_commission', swapType, swapAmount) + }, + { + name: 'fromToken_double_commission', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'fromToken_double_commission', swapType, chain), + description: `${swapType} with double fromToken commission`, + extraValue: ScenarioBuilder.calculateExtraValue('fromToken_double_commission', swapType, swapAmount) + }, + { + name: 'toToken_single_commission', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'toToken_single_commission', swapType, chain), + description: `${swapType} with single toToken commission`, + extraValue: '0x0' + }, + { + name: 'toToken_double_commission', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'toToken_double_commission', swapType, chain), + description: `${swapType} with double toToken commission`, + extraValue: '0x0' + }, + { + name: 'trim_single', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'trim_single', swapType, chain), + description: `${swapType} with trim single address`, + extraValue: '0x0' + }, + { + name: 'trim_double', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'trim_double', swapType, chain), + description: `${swapType} with trim double address`, + extraValue: '0x0' + }, + { + name: 'max_gas_scenario', + swapType, + calldata: ScenarioBuilder.buildCalldata(baseCalldata, 'max_gas_scenario', swapType, chain), + description: `${swapType} with toToken double commission + trim double address`, + extraValue: ScenarioBuilder.calculateExtraValue('max_gas_scenario', swapType, swapAmount) + } + ]; + + return scenarios; + } +} + +// CLI test +if (require.main === module) { + console.log('=== Scenario Builder Test ===\n'); + + const baseCalldata = config.exampleCalldata.arb.dagSwap; + + console.log('Base calldata:', baseCalldata.slice(0, 66) + '...\n'); + + // Generate all scenarios + const scenarios = ScenarioBuilder.generateAllScenarios(baseCalldata, 'ERC20->ETH'); + + scenarios.forEach((scenario, i) => { + console.log(`${i + 1}. ${scenario.name}`); + console.log(` Description: ${scenario.description}`); + console.log(` Calldata length: ${scenario.calldata.length} chars`); + console.log(` Suffix length: ${scenario.calldata.length - baseCalldata.length} chars`); + console.log(); + }); + + console.log('All scenarios generated with different calldata lengths\n'); +} + +module.exports = ScenarioBuilder; diff --git a/gasAnalyzer/package-lock.json b/gasAnalyzer/package-lock.json new file mode 100644 index 0000000..841ae41 --- /dev/null +++ b/gasAnalyzer/package-lock.json @@ -0,0 +1,891 @@ +{ + "name": "gasAnalyzer", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "ethers": "^5.8.0", + "gitignore": "^0.7.0" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", + "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", + "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", + "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", + "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/rlp": "^5.8.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", + "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.8.0.tgz", + "integrity": "sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", + "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", + "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.8.0.tgz", + "integrity": "sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", + "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.8.0.tgz", + "integrity": "sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz", + "integrity": "sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", + "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz", + "integrity": "sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/sha2": "^5.8.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.8.0.tgz", + "integrity": "sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0", + "bech32": "1.1.4", + "ws": "8.18.0" + } + }, + "node_modules/@ethersproject/random": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.8.0.tgz", + "integrity": "sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", + "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz", + "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "bn.js": "^5.2.1", + "elliptic": "6.6.1", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.8.0.tgz", + "integrity": "sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", + "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.8.0.tgz", + "integrity": "sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.8.0.tgz", + "integrity": "sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/json-wallets": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", + "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.8.0.tgz", + "integrity": "sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "license": "MIT" + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/ethers": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz", + "integrity": "sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "5.8.0", + "@ethersproject/abstract-provider": "5.8.0", + "@ethersproject/abstract-signer": "5.8.0", + "@ethersproject/address": "5.8.0", + "@ethersproject/base64": "5.8.0", + "@ethersproject/basex": "5.8.0", + "@ethersproject/bignumber": "5.8.0", + "@ethersproject/bytes": "5.8.0", + "@ethersproject/constants": "5.8.0", + "@ethersproject/contracts": "5.8.0", + "@ethersproject/hash": "5.8.0", + "@ethersproject/hdnode": "5.8.0", + "@ethersproject/json-wallets": "5.8.0", + "@ethersproject/keccak256": "5.8.0", + "@ethersproject/logger": "5.8.0", + "@ethersproject/networks": "5.8.0", + "@ethersproject/pbkdf2": "5.8.0", + "@ethersproject/properties": "5.8.0", + "@ethersproject/providers": "5.8.0", + "@ethersproject/random": "5.8.0", + "@ethersproject/rlp": "5.8.0", + "@ethersproject/sha2": "5.8.0", + "@ethersproject/signing-key": "5.8.0", + "@ethersproject/solidity": "5.8.0", + "@ethersproject/strings": "5.8.0", + "@ethersproject/transactions": "5.8.0", + "@ethersproject/units": "5.8.0", + "@ethersproject/wallet": "5.8.0", + "@ethersproject/web": "5.8.0", + "@ethersproject/wordlists": "5.8.0" + } + }, + "node_modules/gitignore": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/gitignore/-/gitignore-0.7.0.tgz", + "integrity": "sha512-6iE891OyeYQYVvdoWI/hcxDWJ0sOngSpIRadxLoYbsnZdqWIUsEfx+IrOpW3d/zWBA/eKYvs6ZZx6ogz2wEGoQ==", + "license": "MIT", + "bin": { + "gitignore": "bin/gitignore.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/gasAnalyzer/package.json b/gasAnalyzer/package.json new file mode 100644 index 0000000..158c060 --- /dev/null +++ b/gasAnalyzer/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "ethers": "^5.8.0", + "gitignore": "^0.7.0" + } +} diff --git a/gasAnalyzer/result/gas-analysis-arb-uniswapV3-1765773539889.csv b/gasAnalyzer/result/gas-analysis-arb-uniswapV3-1765773539889.csv new file mode 100644 index 0000000..70ec2b3 --- /dev/null +++ b/gasAnalyzer/result/gas-analysis-arb-uniswapV3-1765773539889.csv @@ -0,0 +1,19 @@ +Chain,Method,Scenario,SwapType,BeforeSwap,TotalAdapterGas,AfterSwap,Total,NumAdapters,Timestamp +arb,uniswapV3,basic,ERC20->ERC20,10370,114521,39643,164534,1,2025-12-15T04:38:36.710Z +arb,uniswapV3,fromToken_single_commission,ERC20->ERC20,101724,80921,27366,210011,1,2025-12-15T04:38:37.959Z +arb,uniswapV3,fromToken_double_commission,ERC20->ERC20,111887,80921,31687,224495,1,2025-12-15T04:38:39.411Z +arb,uniswapV3,toToken_single_commission,ERC20->ERC20,13666,114521,76885,205072,1,2025-12-15T04:38:40.659Z +arb,uniswapV3,toToken_double_commission,ERC20->ERC20,13666,114521,85208,213395,1,2025-12-15T04:38:41.887Z +arb,uniswapV3,max_gas_scenario,ERC20->ERC20,13666,114521,121370,249557,1,2025-12-15T04:38:43.126Z +arb,uniswapV3,basic,ETH->ERC20,46961,63122,29155,139238,1,2025-12-15T04:38:44.359Z +arb,uniswapV3,fromToken_single_commission,ETH->ERC20,60288,63122,29365,152775,1,2025-12-15T04:38:45.753Z +arb,uniswapV3,fromToken_double_commission,ETH->ERC20,73615,63122,29154,165891,1,2025-12-15T04:38:47.017Z +arb,uniswapV3,toToken_single_commission,ETH->ERC20,50211,80222,51577,182010,1,2025-12-15T04:38:48.426Z +arb,uniswapV3,toToken_double_commission,ETH->ERC20,50211,80222,61063,191496,1,2025-12-15T04:38:49.816Z +arb,uniswapV3,max_gas_scenario,ETH->ERC20,50211,80222,76820,207253,1,2025-12-15T04:38:51.046Z +arb,uniswapV3,basic,ERC20->ETH,574,125521,61718,187813,1,2025-12-15T04:38:52.599Z +arb,uniswapV3,fromToken_single_commission,ERC20->ETH,91928,91921,52783,236632,1,2025-12-15T04:38:53.857Z +arb,uniswapV3,fromToken_double_commission,ERC20->ETH,102091,91921,54208,248220,1,2025-12-15T04:38:55.119Z +arb,uniswapV3,toToken_single_commission,ERC20->ETH,574,125521,82866,208961,1,2025-12-15T04:38:56.347Z +arb,uniswapV3,toToken_double_commission,ERC20->ETH,574,125521,93591,219686,1,2025-12-15T04:38:57.577Z +arb,uniswapV3,max_gas_scenario,ERC20->ETH,574,125521,115146,241241,1,2025-12-15T04:38:58.886Z \ No newline at end of file diff --git a/gasAnalyzer/result/gas-analysis-arb-uniswapV3-1765773539891.json b/gasAnalyzer/result/gas-analysis-arb-uniswapV3-1765773539891.json new file mode 100644 index 0000000..68adb5e --- /dev/null +++ b/gasAnalyzer/result/gas-analysis-arb-uniswapV3-1765773539891.json @@ -0,0 +1,496 @@ +{ + "chain": "arb", + "chainName": "Arbitrum One", + "contract": "0x368E01160C2244B0363a35B3fF0A971E44a89284", + "method": "uniswapV3", + "methodName": "uniswapV3SwapTo", + "timestamp": "2025-12-15T04:38:59.891Z", + "results": [ + { + "scenario": "basic", + "swapType": "ERC20->ERC20", + "description": "Basic ERC20->ERC20 swap", + "breakdown": { + "total": "0x282b6", + "totalDecimal": 164534, + "beforeSwap": 10370, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 114521, + "type": "CALL" + } + ], + "totalAdapterGas": 114521, + "afterSwap": 39643, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 164534 + } + }, + "timestamp": "2025-12-15T04:38:36.710Z" + }, + { + "scenario": "fromToken_single_commission", + "swapType": "ERC20->ERC20", + "description": "ERC20->ERC20 with single fromToken commission", + "breakdown": { + "total": "0x3345b", + "totalDecimal": 210011, + "beforeSwap": 101724, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 80921, + "type": "CALL" + } + ], + "totalAdapterGas": 80921, + "afterSwap": 27366, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 210011 + } + }, + "timestamp": "2025-12-15T04:38:37.959Z" + }, + { + "scenario": "fromToken_double_commission", + "swapType": "ERC20->ERC20", + "description": "ERC20->ERC20 with double fromToken commission", + "breakdown": { + "total": "0x36cef", + "totalDecimal": 224495, + "beforeSwap": 111887, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 80921, + "type": "CALL" + } + ], + "totalAdapterGas": 80921, + "afterSwap": 31687, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 224495 + } + }, + "timestamp": "2025-12-15T04:38:39.411Z" + }, + { + "scenario": "toToken_single_commission", + "swapType": "ERC20->ERC20", + "description": "ERC20->ERC20 with single toToken commission", + "breakdown": { + "total": "0x32110", + "totalDecimal": 205072, + "beforeSwap": 13666, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 114521, + "type": "CALL" + } + ], + "totalAdapterGas": 114521, + "afterSwap": 76885, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 205072 + } + }, + "timestamp": "2025-12-15T04:38:40.659Z" + }, + { + "scenario": "toToken_double_commission", + "swapType": "ERC20->ERC20", + "description": "ERC20->ERC20 with double toToken commission", + "breakdown": { + "total": "0x34193", + "totalDecimal": 213395, + "beforeSwap": 13666, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 114521, + "type": "CALL" + } + ], + "totalAdapterGas": 114521, + "afterSwap": 85208, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 213395 + } + }, + "timestamp": "2025-12-15T04:38:41.887Z" + }, + { + "scenario": "max_gas_scenario", + "swapType": "ERC20->ERC20", + "description": "ERC20->ERC20 max gas (double commission + TRIM)", + "breakdown": { + "total": "0x3ced5", + "totalDecimal": 249557, + "beforeSwap": 13666, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 114521, + "type": "CALL" + } + ], + "totalAdapterGas": 114521, + "afterSwap": 121370, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 249557 + } + }, + "timestamp": "2025-12-15T04:38:43.126Z" + }, + { + "scenario": "basic", + "swapType": "ETH->ERC20", + "description": "Basic ETH->ERC20 swap", + "breakdown": { + "total": "0x21fe6", + "totalDecimal": 139238, + "beforeSwap": 46961, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 63122, + "type": "CALL" + } + ], + "totalAdapterGas": 63122, + "afterSwap": 29155, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 139238 + } + }, + "timestamp": "2025-12-15T04:38:44.359Z" + }, + { + "scenario": "fromToken_single_commission", + "swapType": "ETH->ERC20", + "description": "ETH->ERC20 with single fromToken commission", + "breakdown": { + "total": "0x254c7", + "totalDecimal": 152775, + "beforeSwap": 60288, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 63122, + "type": "CALL" + } + ], + "totalAdapterGas": 63122, + "afterSwap": 29365, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 152775 + } + }, + "timestamp": "2025-12-15T04:38:45.753Z" + }, + { + "scenario": "fromToken_double_commission", + "swapType": "ETH->ERC20", + "description": "ETH->ERC20 with double fromToken commission", + "breakdown": { + "total": "0x28803", + "totalDecimal": 165891, + "beforeSwap": 73615, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 63122, + "type": "CALL" + } + ], + "totalAdapterGas": 63122, + "afterSwap": 29154, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 165891 + } + }, + "timestamp": "2025-12-15T04:38:47.017Z" + }, + { + "scenario": "toToken_single_commission", + "swapType": "ETH->ERC20", + "description": "ETH->ERC20 with single toToken commission", + "breakdown": { + "total": "0x2c6fa", + "totalDecimal": 182010, + "beforeSwap": 50211, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 80222, + "type": "CALL" + } + ], + "totalAdapterGas": 80222, + "afterSwap": 51577, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 182010 + } + }, + "timestamp": "2025-12-15T04:38:48.426Z" + }, + { + "scenario": "toToken_double_commission", + "swapType": "ETH->ERC20", + "description": "ETH->ERC20 with double toToken commission", + "breakdown": { + "total": "0x2ec08", + "totalDecimal": 191496, + "beforeSwap": 50211, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 80222, + "type": "CALL" + } + ], + "totalAdapterGas": 80222, + "afterSwap": 61063, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 191496 + } + }, + "timestamp": "2025-12-15T04:38:49.816Z" + }, + { + "scenario": "max_gas_scenario", + "swapType": "ETH->ERC20", + "description": "ETH->ERC20 max gas (double commission + TRIM)", + "breakdown": { + "total": "0x32995", + "totalDecimal": 207253, + "beforeSwap": 50211, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 80222, + "type": "CALL" + } + ], + "totalAdapterGas": 80222, + "afterSwap": 76820, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 207253 + } + }, + "timestamp": "2025-12-15T04:38:51.046Z" + }, + { + "scenario": "basic", + "swapType": "ERC20->ETH", + "description": "Basic ERC20->ETH swap", + "breakdown": { + "total": "0x2dda5", + "totalDecimal": 187813, + "beforeSwap": 574, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 125521, + "type": "CALL" + } + ], + "totalAdapterGas": 125521, + "afterSwap": 61718, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 187813 + } + }, + "timestamp": "2025-12-15T04:38:52.599Z" + }, + { + "scenario": "fromToken_single_commission", + "swapType": "ERC20->ETH", + "description": "ERC20->ETH with single fromToken commission", + "breakdown": { + "total": "0x39c58", + "totalDecimal": 236632, + "beforeSwap": 91928, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 91921, + "type": "CALL" + } + ], + "totalAdapterGas": 91921, + "afterSwap": 52783, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 236632 + } + }, + "timestamp": "2025-12-15T04:38:53.857Z" + }, + { + "scenario": "fromToken_double_commission", + "swapType": "ERC20->ETH", + "description": "ERC20->ETH with double fromToken commission", + "breakdown": { + "total": "0x3c99c", + "totalDecimal": 248220, + "beforeSwap": 102091, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 91921, + "type": "CALL" + } + ], + "totalAdapterGas": 91921, + "afterSwap": 54208, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 248220 + } + }, + "timestamp": "2025-12-15T04:38:55.119Z" + }, + { + "scenario": "toToken_single_commission", + "swapType": "ERC20->ETH", + "description": "ERC20->ETH with single toToken commission", + "breakdown": { + "total": "0x33041", + "totalDecimal": 208961, + "beforeSwap": 574, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 125521, + "type": "CALL" + } + ], + "totalAdapterGas": 125521, + "afterSwap": 82866, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 208961 + } + }, + "timestamp": "2025-12-15T04:38:56.347Z" + }, + { + "scenario": "toToken_double_commission", + "swapType": "ERC20->ETH", + "description": "ERC20->ETH with double toToken commission", + "breakdown": { + "total": "0x35a26", + "totalDecimal": 219686, + "beforeSwap": 574, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 125521, + "type": "CALL" + } + ], + "totalAdapterGas": 125521, + "afterSwap": 93591, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 219686 + } + }, + "timestamp": "2025-12-15T04:38:57.577Z" + }, + { + "scenario": "max_gas_scenario", + "swapType": "ERC20->ETH", + "description": "ERC20->ETH max gas (double commission + TRIM)", + "breakdown": { + "total": "0x3ae59", + "totalDecimal": 241241, + "beforeSwap": 574, + "adapters": [ + { + "name": "uniswapV3_adapter", + "selector": "0x128acb08", + "to": "0xc6962004f452be9203591991d15f6b388e09e8d0", + "gasUsed": 125521, + "type": "CALL" + } + ], + "totalAdapterGas": 125521, + "afterSwap": 115146, + "mainMethod": { + "name": "uniswapV3SwapTo", + "selector": "0x0d5f0e3b", + "gasUsed": 241241 + } + }, + "timestamp": "2025-12-15T04:38:58.886Z" + } + ] +} \ No newline at end of file diff --git a/gasAnalyzer/result/quick-test-trace.json b/gasAnalyzer/result/quick-test-trace.json new file mode 100644 index 0000000..2091ae7 --- /dev/null +++ b/gasAnalyzer/result/quick-test-trace.json @@ -0,0 +1,117 @@ +{ + "beforeEVMTransfers": [ + { + "purpose": "feePayment", + "from": "0xF2048e7a1D4c19F658c19b3Cd35369f9f96223aF", + "to": null, + "value": "0x0" + } + ], + "afterEVMTransfers": [ + { + "purpose": "gasRefund", + "from": null, + "to": "0xF2048e7a1D4c19F658c19b3Cd35369f9f96223aF", + "value": "0x0" + }, + { + "purpose": "feeCollection", + "from": null, + "to": "0xbF5041Fc07E1c866D15c749156657B8eEd0fb649", + "value": "0xbfea520f80" + }, + { + "purpose": "feeCollection", + "from": null, + "to": "0xa4B00000000000000000000000000000000000F6", + "value": "0x0" + } + ], + "from": "0xf2048e7a1d4c19f658c19b3cd35369f9f96223af", + "gas": "0x23c34600", + "gasUsed": "0x141fb", + "to": "0x368e01160c2244b0363a35b3ff0a971e44a89284", + "input": "0xf2c42696000000000000000000000000000000000000000000000000003a5fb5ff172280000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000303798f000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006f308f9a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000160000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb900000000000000000000000000000000000000000000000000000000000000010000000000000000000000004df8e917de87c0de5bff92b73a3664412bb3ae770000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fc43aaf89a71acaa644842ee4219e8eb776574270000000000000000000000000000000000000000000000000000000000000001000000000000000000012710fc43aaf89a71acaa644842ee4219e8eb77657427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", + "output": "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000205361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564", + "error": "execution reverted", + "revertReason": "SafeERC20: low-level call failed", + "calls": [ + { + "from": "0x368e01160c2244b0363a35b3ff0a971e44a89284", + "gas": "0x2333b306", + "gasUsed": "0x2616", + "to": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", + "input": "0x70a08231000000000000000000000000f2048e7a1d4c19f658c19b3cd35369f9f96223af", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "calls": [ + { + "from": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", + "gas": "0x22a6c8b6", + "gasUsed": "0x9f9", + "to": "0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3", + "input": "0x70a08231000000000000000000000000f2048e7a1d4c19f658c19b3cd35369f9f96223af", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "DELEGATECALL" + } + ], + "type": "STATICCALL" + }, + { + "from": "0x368e01160c2244b0363a35b3ff0a971e44a89284", + "gas": "0x23336bd7", + "gasUsed": "0x7176", + "to": "0xe9bbd6ec0c9ca71d3dccd1282ee9de4f811e50af", + "input": "0x0a5ea466000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9000000000000000000000000f2048e7a1d4c19f658c19b3cd35369f9f96223af000000000000000000000000fc43aaf89a71acaa644842ee4219e8eb776574270000000000000000000000000000000000000000000000000000000303798f00", + "output": "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000205361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564", + "error": "execution reverted", + "revertReason": "SafeERC20: low-level call failed", + "calls": [ + { + "from": "0xe9bbd6ec0c9ca71d3dccd1282ee9de4f811e50af", + "gas": "0x22a68076", + "gasUsed": "0x5319", + "to": "0x70cbb871e8f30fc8ce23609e9e0ea87b6b222f58", + "input": "0x0a5ea466000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9000000000000000000000000f2048e7a1d4c19f658c19b3cd35369f9f96223af000000000000000000000000fc43aaf89a71acaa644842ee4219e8eb776574270000000000000000000000000000000000000000000000000000000303798f00", + "output": "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000205361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564", + "error": "execution reverted", + "revertReason": "SafeERC20: low-level call failed", + "calls": [ + { + "from": "0x70cbb871e8f30fc8ce23609e9e0ea87b6b222f58", + "gas": "0x221bce56", + "gasUsed": "0x3986", + "to": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", + "input": "0x23b872dd000000000000000000000000f2048e7a1d4c19f658c19b3cd35369f9f96223af000000000000000000000000fc43aaf89a71acaa644842ee4219e8eb776574270000000000000000000000000000000000000000000000000000000303798f00", + "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002645524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000000000000000000000", + "error": "execution reverted", + "revertReason": "ERC20: transfer amount exceeds balance", + "calls": [ + { + "from": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", + "gas": "0x2193430c", + "gasUsed": "0x1cca", + "to": "0x3263cd783823d04a6b9819517e0e6840d37ca3f4", + "input": "0x23b872dd000000000000000000000000f2048e7a1d4c19f658c19b3cd35369f9f96223af000000000000000000000000fc43aaf89a71acaa644842ee4219e8eb776574270000000000000000000000000000000000000000000000000000000303798f00", + "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002645524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000000000000000000000", + "error": "execution reverted", + "revertReason": "ERC20: transfer amount exceeds balance", + "value": "0x0", + "type": "DELEGATECALL" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" +} \ No newline at end of file diff --git a/gasAnalyzer/utils/fetchPoolInfo.js b/gasAnalyzer/utils/fetchPoolInfo.js new file mode 100644 index 0000000..9a6e023 --- /dev/null +++ b/gasAnalyzer/utils/fetchPoolInfo.js @@ -0,0 +1,179 @@ +#!/usr/bin/env node +/** + * Fetch pool information from chain + * Automatically gets token0, token1, sqrtPriceX96 for UniV3 pools + */ + +const { ethers } = require('ethers'); +const config = require('../config/chains'); + +// UniswapV3 Pool ABI (minimal) +const UNIV3_POOL_ABI = [ + 'function token0() external view returns (address)', + 'function token1() external view returns (address)', + 'function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint32 feeProtocol, bool unlocked)', + 'function fee() external view returns (uint24)' +]; + +// UniswapV2 Pool ABI (minimal) +const UNIV2_POOL_ABI = [ + 'function token0() external view returns (address)', + 'function token1() external view returns (address)', + 'function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)' +]; + +/** + * Fetch UniV3 pool info from chain + */ +async function fetchUniV3PoolInfo(chain, poolAddress) { + const chainConfig = config.chains[chain]; + const provider = new ethers.providers.JsonRpcProvider(chainConfig.rpcUrl); + + const pool = new ethers.Contract(poolAddress, UNIV3_POOL_ABI, provider); + + console.log(`Fetching UniV3 pool info from ${chain}...`); + console.log(`Pool: ${poolAddress}\n`); + + try { + const [token0, token1, slot0, fee] = await Promise.all([ + pool.token0(), + pool.token1(), + pool.slot0(), + pool.fee().catch(() => null) // Some pools don't have fee() + ]); + + const info = { + pool: poolAddress, + token0: token0, + token1: token1, + sqrtPriceX96: slot0.sqrtPriceX96.toString(), + tick: slot0.tick, + fee: fee ? fee.toString() : 'unknown' + }; + + console.log('Pool Info:'); + console.log(` token0: ${info.token0}`); + console.log(` token1: ${info.token1}`); + console.log(` sqrtPriceX96: ${info.sqrtPriceX96}`); + console.log(` tick: ${info.tick}`); + console.log(` fee: ${info.fee}`); + + return info; + } catch (error) { + console.error('Error fetching pool info:', error.message); + throw error; + } +} + +/** + * Fetch UniV2 pool info from chain + */ +async function fetchUniV2PoolInfo(chain, poolAddress) { + const chainConfig = config.chains[chain]; + const provider = new ethers.providers.JsonRpcProvider(chainConfig.rpcUrl); + + const pool = new ethers.Contract(poolAddress, UNIV2_POOL_ABI, provider); + + console.log(`Fetching UniV2 pool info from ${chain}...`); + console.log(`Pool: ${poolAddress}\n`); + + try { + const [token0, token1, reserves] = await Promise.all([ + pool.token0(), + pool.token1(), + pool.getReserves() + ]); + + const info = { + pool: poolAddress, + token0: token0, + token1: token1, + reserve0: reserves.reserve0.toString(), + reserve1: reserves.reserve1.toString() + }; + + console.log('Pool Info:'); + console.log(` token0: ${info.token0}`); + console.log(` token1: ${info.token1}`); + console.log(` reserve0: ${info.reserve0}`); + console.log(` reserve1: ${info.reserve1}`); + + return info; + } catch (error) { + console.error('Error fetching pool info:', error.message); + throw error; + } +} + +/** + * Generate pools.js config snippet for a UniV3 dagSwap pool + */ +async function generateUniV3DagSwapConfig(chain, adapterAddress, poolAddress) { + const info = await fetchUniV3PoolInfo(chain, poolAddress); + + console.log('\n=== Generated Config ===\n'); + console.log(`dagSwapV3: { + poolType: 'uniswapV3', + adapter: '${adapterAddress}', + pool: '${poolAddress}', + token0: '${info.token0}', + token1: '${info.token1}', + // sqrtPriceX96 will be fetched dynamically, or use 0 for default + sqrtPriceX96: '0', // Set to 0 to use MIN/MAX_SQRT_RATIO based on direction +},`); + + return { + poolType: 'uniswapV3', + adapter: adapterAddress, + pool: poolAddress, + token0: info.token0, + token1: info.token1, + sqrtPriceX96: '0' + }; +} + +// CLI +if (require.main === module) { + const args = process.argv.slice(2); + + if (args.length < 3) { + console.log('Usage: node fetchPoolInfo.js [adapterAddress]'); + console.log(''); + console.log('Pool Types: uniV2, uniV3'); + console.log('Chains:', Object.keys(config.chains).join(', ')); + console.log(''); + console.log('Examples:'); + console.log(' node fetchPoolInfo.js arb uniV3 0xaa89ba37d1975ae294974ebb33db9d4b5324f2f2'); + console.log(' node fetchPoolInfo.js arb uniV3 0xaa89ba37d1975ae294974ebb33db9d4b5324f2f2 0x6747BcaF9bD5a5F0758Cbe08903490E45DdfACB5'); + process.exit(1); + } + + const [chain, poolType, poolAddress, adapterAddress] = args; + + (async () => { + try { + if (poolType === 'uniV3') { + if (adapterAddress) { + await generateUniV3DagSwapConfig(chain, adapterAddress, poolAddress); + } else { + await fetchUniV3PoolInfo(chain, poolAddress); + } + } else if (poolType === 'uniV2') { + await fetchUniV2PoolInfo(chain, poolAddress); + } else { + console.error('Unknown pool type:', poolType); + process.exit(1); + } + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } + })(); +} + +module.exports = { + fetchUniV3PoolInfo, + fetchUniV2PoolInfo, + generateUniV3DagSwapConfig +}; + diff --git a/gasAnalyzer/utils/quickTest.js b/gasAnalyzer/utils/quickTest.js new file mode 100644 index 0000000..3b0d459 --- /dev/null +++ b/gasAnalyzer/utils/quickTest.js @@ -0,0 +1,84 @@ +#!/usr/bin/env node + +const { ethers } = require('ethers'); +const config = require('../config/chains'); +const fs = require('fs'); +const path = require('path'); + +console.log('======================================================================'); +console.log(' Testing debug_traceCall with QuickNode'); +console.log('======================================================================\n'); + +console.log('=== Quick Test ===\n'); + +const chain = 'arb'; +const chainConfig = config.chains[chain]; + +console.log(`Chain: ${chainConfig.name}`); +console.log(`RPC: ${chainConfig.rpcUrl}`); +console.log(`Contract: ${chainConfig.contract}`); + +(async () => { + try { + // NOTE: ethers v5 uses ethers.providers.JsonRpcProvider + const provider = new ethers.providers.JsonRpcProvider(chainConfig.rpcUrl); + + console.log('\nCalling debug_traceCall...\n'); + + const response = await provider.send('debug_traceCall', [ + { + from: '0xF2048e7a1D4c19F658c19b3Cd35369f9f96223aF', + to: chainConfig.contract, + data: config.exampleCalldata[chain].dagSwap, + value: '0x0' + }, + 'latest', + { + tracer: 'callTracer' + } + ]); + + console.log('✅ Success! Response received:\n'); + + const gasUsed = parseInt(response.gasUsed, 16); + console.log(`Type: ${response.type}`); + console.log(`From: ${response.from}`); + console.log(`To: ${response.to}`); + console.log(`Gas Used: ${gasUsed.toLocaleString()}`); + console.log(`Number of calls: ${response.calls?.length || 0}`); + + if (response.calls && response.calls.length > 0) { + console.log('\nFirst-level calls:'); + response.calls.forEach((call, i) => { + const callGas = parseInt(call.gasUsed, 16); + console.log(` ${i + 1}. ${call.type} to ${call.to.slice(0, 12)}... - Gas: ${callGas.toLocaleString()}`); + }); + } + + // Save trace to result folder + const resultDir = path.join(__dirname, '..', 'result'); + if (!fs.existsSync(resultDir)) { + fs.mkdirSync(resultDir, { recursive: true }); + } + const traceFile = path.join(resultDir, 'quick-test-trace.json'); + fs.writeFileSync(traceFile, JSON.stringify(response, null, 2)); + console.log(`\nFull trace saved to: ${traceFile}`); + + console.log('\n🎉 Your RPC endpoint works with debug_traceCall!\n'); + console.log('You can now run:'); + console.log(' node analyzer/gasAnalyzer.js arb dagSwap all'); + console.log(' node utils/verify_scenarios.js\n'); + + } catch (error) { + console.error('❌ Error:', error.message); + + if (error.message.includes('Method not found') || error.message.includes('not supported')) { + console.error('\n⚠️ The RPC endpoint does not support debug_traceCall.'); + console.error('Please update config/chains.js with your QuickNode endpoint that supports debug APIs.'); + console.error('\nGet QuickNode: https://www.quicknode.com/'); + } + + process.exit(1); + } +})(); + diff --git a/gasAnalyzer/utils/verify_scenarios.js b/gasAnalyzer/utils/verify_scenarios.js new file mode 100644 index 0000000..685cd45 --- /dev/null +++ b/gasAnalyzer/utils/verify_scenarios.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +const CalldataEncoder = require('../encoder/calldataEncoder'); +const ScenarioBuilder = require('../encoder/scenarioBuilder'); +const poolConfig = require('../config/pools'); + +/** + * Verify that scenario generation works correctly + */ +async function verifyScenarios() { + console.log('='.repeat(80)); + console.log('SCENARIO GENERATION VERIFICATION'); + console.log('='.repeat(80)); + console.log(); + + const chains = ['arb']; + const methods = ['uniswapV3']; + const swapTypes = ['ERC20->ERC20']; + + for (const chain of chains) { + console.log(`\nCHAIN: ${chain.toUpperCase()}\n`); + + for (const method of methods) { + console.log(`Method: ${method}\n`); + + for (const swapType of swapTypes) { + try { + // Generate base calldata + const baseResult = CalldataEncoder.generate(method, chain, swapType); + const baseCalldata = baseResult.calldata; + + console.log(`${swapType}:`); + console.log(` Base: ${baseCalldata.slice(0, 66)}...`); + console.log(` Length: ${baseCalldata.length} chars\n`); + + // Generate all scenarios (pass swapAmount for commission calculation) + const scenarios = ScenarioBuilder.generateAllScenarios(baseCalldata, swapType, baseResult.value); + + console.log(`Scenarios (${scenarios.length}):\n`); + + scenarios.forEach((scenario, i) => { + const suffix = scenario.calldata.slice(baseCalldata.length); + console.log(`${i + 1}. ${scenario.name.padEnd(35)} +${suffix.length} chars`); + }); + + console.log('\n✓ All scenarios generated successfully\n'); + + } catch (error) { + console.log(`✗ Error: ${error.message}\n`); + } + } + } + } +} + +verifyScenarios().catch(error => { + console.error('Error:', error); + process.exit(1); +}); +