From 7d9fd199083f7c6219ff5bc881733fb20e853c58 Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Sat, 18 Jan 2025 18:42:27 +1100 Subject: [PATCH] force fee tests to run sequentially? --- core/tests/ts-integration/tests/fees.test.ts | 488 ++++++++++--------- 1 file changed, 253 insertions(+), 235 deletions(-) diff --git a/core/tests/ts-integration/tests/fees.test.ts b/core/tests/ts-integration/tests/fees.test.ts index b905d908c3ab..a5d555eb8b60 100644 --- a/core/tests/ts-integration/tests/fees.test.ts +++ b/core/tests/ts-integration/tests/fees.test.ts @@ -160,264 +160,282 @@ testFees('Test fees', function () { } }); - test('Test all fees', async () => { - const receiver = ethers.Wallet.createRandom().address; - - // Getting ETH price in gas. - const feeTestL1Receipt = await ( - await alice.ethWallet().sendTransaction({ - to: receiver, - value: 1n - }) - ).wait(); - - if (feeTestL1Receipt === null) { - throw new Error('Failed to send ETH transaction'); - } + describe('Test all fees', function () { + test('works', async () => { + const receiver = ethers.Wallet.createRandom().address; + + // Getting ETH price in gas. + const feeTestL1Receipt = await ( + await alice.ethWallet().sendTransaction({ + to: receiver, + value: 1n + }) + ).wait(); + + if (feeTestL1Receipt === null) { + throw new Error('Failed to send ETH transaction'); + } - const feeTestL1ReceiptERC20 = await ( - await alice.ethWallet().sendTransaction({ - to: aliceErc20.getAddress(), - data: aliceErc20.interface.encodeFunctionData('transfer', [receiver, 1n]) - }) - ).wait(); + const feeTestL1ReceiptERC20 = await ( + await alice.ethWallet().sendTransaction({ + to: aliceErc20.getAddress(), + data: aliceErc20.interface.encodeFunctionData('transfer', [receiver, 1n]) + }) + ).wait(); - if (feeTestL1ReceiptERC20 === null) { - throw new Error('Failed to send ERC20 transaction'); - } - - // Warming up slots for the receiver - await ( - await alice.sendTransaction({ - to: receiver, - value: BigInt(1), - type: 2 - }) - ).wait(); - - await ( - await alice.sendTransaction({ - data: aliceErc20.interface.encodeFunctionData('transfer', [receiver, 1n]), - to: tokenDetails.l2Address, - type: 2 - }) - ).wait(); - - let reports = [ - 'ETH transfer (to new):\n\n', - 'ETH transfer (to old):\n\n', - 'ERC20 transfer (to new):\n\n', - 'ERC20 transfer (to old):\n\n' - ]; - for (const gasPrice of L1_GAS_PRICES_TO_TEST) { - // For the sake of simplicity, we'll use the same pubdata price as the L1 gas price. - await mainNodeSpawner.killAndSpawnMainNode({ - newL1GasPrice: gasPrice, - newPubdataPrice: gasPrice - }); + if (feeTestL1ReceiptERC20 === null) { + throw new Error('Failed to send ERC20 transaction'); + } - reports = await appendResults( - alice, - [feeTestL1Receipt, feeTestL1Receipt, feeTestL1ReceiptERC20, feeTestL1ReceiptERC20], - // We always regenerate new addresses for transaction requests in order to estimate the cost for a new account - [ - { - to: ethers.Wallet.createRandom().address, - value: 1n, - type: 2 - }, - { - to: receiver, - value: 1n, - type: 2 - }, - { - data: aliceErc20.interface.encodeFunctionData('transfer', [ - ethers.Wallet.createRandom().address, - 1n - ]), - to: tokenDetails.l2Address, - type: 2 - }, - { - data: aliceErc20.interface.encodeFunctionData('transfer', [receiver, 1n]), - to: tokenDetails.l2Address, - type: 2 - } - ], - gasPrice, - reports - ); - } + // Warming up slots for the receiver + await ( + await alice.sendTransaction({ + to: receiver, + value: BigInt(1), + type: 2 + }) + ).wait(); + + await ( + await alice.sendTransaction({ + data: aliceErc20.interface.encodeFunctionData('transfer', [receiver, 1n]), + to: tokenDetails.l2Address, + type: 2 + }) + ).wait(); + + let reports = [ + 'ETH transfer (to new):\n\n', + 'ETH transfer (to old):\n\n', + 'ERC20 transfer (to new):\n\n', + 'ERC20 transfer (to old):\n\n' + ]; + for (const gasPrice of L1_GAS_PRICES_TO_TEST) { + // For the sake of simplicity, we'll use the same pubdata price as the L1 gas price. + await mainNodeSpawner.killAndSpawnMainNode({ + newL1GasPrice: gasPrice, + newPubdataPrice: gasPrice + }); + + reports = await appendResults( + alice, + [feeTestL1Receipt, feeTestL1Receipt, feeTestL1ReceiptERC20, feeTestL1ReceiptERC20], + // We always regenerate new addresses for transaction requests in order to estimate the cost for a new account + [ + { + to: ethers.Wallet.createRandom().address, + value: 1n, + type: 2 + }, + { + to: receiver, + value: 1n, + type: 2 + }, + { + data: aliceErc20.interface.encodeFunctionData('transfer', [ + ethers.Wallet.createRandom().address, + 1n + ]), + to: tokenDetails.l2Address, + type: 2 + }, + { + data: aliceErc20.interface.encodeFunctionData('transfer', [receiver, 1n]), + to: tokenDetails.l2Address, + type: 2 + } + ], + gasPrice, + reports + ); + } - console.log(`Full report: \n\n${reports.join('\n\n')}`); + console.log(`Full report: \n\n${reports.join('\n\n')}`); + }); }); - test('Test gas price expected value', async () => { - const l1GasPrice = 2_000_000_000n; /// set to 2 gwei - await mainNodeSpawner.killAndSpawnMainNode({ - newL1GasPrice: l1GasPrice, - newPubdataPrice: l1GasPrice + describe('Test gas price expected value', function () { + test('works', async () => { + const l1GasPrice = 2_000_000_000n; /// set to 2 gwei + await mainNodeSpawner.killAndSpawnMainNode({ + newL1GasPrice: l1GasPrice, + newPubdataPrice: l1GasPrice + }); + console.log('TGSPEV: Node respawned'); + + // wait for new batch so gas price is updated with new config set above + await waitForNewL1Batch(alice); + console.log('TGSPEV: L1 batch sealed'); + + const receipt = await anyTransaction(alice); + console.log(`TGSPEV: TX executed ${JSON.stringify(receipt)}`); + + const feeParams = await alice._providerL2().getFeeParams(); + console.log(`TGSPEV: Fee params fetched ${JSON.stringify(feeParams)}`); + const feeConfig = feeParams.V2.config; + // type is missing conversion_ratio field + const conversionRatio: { numerator: bigint; denominator: bigint } = (feeParams.V2 as any)[ + 'conversion_ratio' + ]; + if (isETHBasedChain) { + expect(conversionRatio.numerator).toBe(1); //number not bigint for some reason + expect(conversionRatio.denominator).toBe(1); + } else { + expect(conversionRatio.numerator).toBeGreaterThan(1n); + } + console.log(`TGSPEV: Conversion checked ${JSON.stringify(conversionRatio)}`); + + // the minimum + compute overhead of 0.01gwei in validium mode + const expectedETHGasPrice = + feeConfig.minimal_l2_gas_price + + (feeConfig.compute_overhead_part * feeParams.V2.l1_gas_price * feeConfig.batch_overhead_l1_gas) / + feeConfig.max_gas_per_batch; + const expectedConvertedGasPrice = + (expectedETHGasPrice * conversionRatio.numerator) / conversionRatio.denominator; + console.log(`TGSPEV: Node has been respawned: ${expectedConvertedGasPrice}`); + + expect(receipt.gasPrice).toBe(BigInt(expectedConvertedGasPrice)); }); - console.log('TGSPEV: Node respawned'); - - // wait for new batch so gas price is updated with new config set above - await waitForNewL1Batch(alice); - console.log('TGSPEV: L1 batch sealed'); - - const receipt = await anyTransaction(alice); - console.log(`TGSPEV: TX executed ${JSON.stringify(receipt)}`); - - const feeParams = await alice._providerL2().getFeeParams(); - console.log(`TGSPEV: Fee params fetched ${JSON.stringify(feeParams)}`); - const feeConfig = feeParams.V2.config; - // type is missing conversion_ratio field - const conversionRatio: { numerator: bigint; denominator: bigint } = (feeParams.V2 as any)['conversion_ratio']; - if (isETHBasedChain) { - expect(conversionRatio.numerator).toBe(1); //number not bigint for some reason - expect(conversionRatio.denominator).toBe(1); - } else { - expect(conversionRatio.numerator).toBeGreaterThan(1n); - } - console.log(`TGSPEV: Conversion checked ${JSON.stringify(conversionRatio)}`); - - // the minimum + compute overhead of 0.01gwei in validium mode - const expectedETHGasPrice = - feeConfig.minimal_l2_gas_price + - (feeConfig.compute_overhead_part * feeParams.V2.l1_gas_price * feeConfig.batch_overhead_l1_gas) / - feeConfig.max_gas_per_batch; - const expectedConvertedGasPrice = - (expectedETHGasPrice * conversionRatio.numerator) / conversionRatio.denominator; - console.log(`TGSPEV: Node has been respawned: ${expectedConvertedGasPrice}`); - - expect(receipt.gasPrice).toBe(BigInt(expectedConvertedGasPrice)); }); - test('Test base token ratio fluctuations', async () => { - const l1GasPrice = 2_000_000_000n; /// set to 2 gwei + describe('Test gas price expected value', function () { + test('works', async () => { + const l1GasPrice = 2_000_000_000n; /// set to 2 gwei - if (isETHBasedChain) return; + if (isETHBasedChain) return; - await mainNodeSpawner.killAndSpawnMainNode({ - newL1GasPrice: l1GasPrice, - newPubdataPrice: l1GasPrice, - externalPriceApiClientForcedNumerator: 300, - externalPriceApiClientForcedDenominator: 100, - externalPriceApiClientForcedFluctuation: 20, - baseTokenPricePollingIntervalMs: 1000, - baseTokenAdjusterL1UpdateDeviationPercentage: 0 - }); + await mainNodeSpawner.killAndSpawnMainNode({ + newL1GasPrice: l1GasPrice, + newPubdataPrice: l1GasPrice, + externalPriceApiClientForcedNumerator: 300, + externalPriceApiClientForcedDenominator: 100, + externalPriceApiClientForcedFluctuation: 20, + baseTokenPricePollingIntervalMs: 1000, + baseTokenAdjusterL1UpdateDeviationPercentage: 0 + }); - const beginFeeParams = await alice._providerL2().getFeeParams(); - const mainContract = await alice.getMainContract(); - const beginL1Nominator = await mainContract.baseTokenGasPriceMultiplierNominator(); - let changedL2 = false; - let changedL1 = false; - for (let i = 0; i < 20; i++) { - await sleep(0.5); - const newFeeParams = await alice._providerL2().getFeeParams(); - // we need any as FeeParams is missing existing conversion_ratio field - - if ( - ((newFeeParams.V2 as any)['conversion_ratio'].numerator as number) != - ((beginFeeParams.V2 as any)['conversion_ratio'].numerator as number) - ) { - // @ts-ignore - const diff = - (newFeeParams.V2 as any)['conversion_ratio'].numerator - - (beginFeeParams.V2 as any)['conversion_ratio'].numerator; - // Deviation is 20%, Adding 5% extra for any arithmetic precision issues, 25%*300 = 75 - expect(diff).toBeLessThan(75); - expect(diff).toBeGreaterThan(-75); - changedL2 = true; - break; + const beginFeeParams = await alice._providerL2().getFeeParams(); + const mainContract = await alice.getMainContract(); + const beginL1Nominator = await mainContract.baseTokenGasPriceMultiplierNominator(); + let changedL2 = false; + let changedL1 = false; + for (let i = 0; i < 20; i++) { + await sleep(0.5); + const newFeeParams = await alice._providerL2().getFeeParams(); + // we need any as FeeParams is missing existing conversion_ratio field + + if ( + ((newFeeParams.V2 as any)['conversion_ratio'].numerator as number) != + ((beginFeeParams.V2 as any)['conversion_ratio'].numerator as number) + ) { + // @ts-ignore + const diff = + (newFeeParams.V2 as any)['conversion_ratio'].numerator - + (beginFeeParams.V2 as any)['conversion_ratio'].numerator; + // Deviation is 20%, Adding 5% extra for any arithmetic precision issues, 25%*300 = 75 + expect(diff).toBeLessThan(75); + expect(diff).toBeGreaterThan(-75); + changedL2 = true; + break; + } } - } - expect(changedL2).toBeTruthy(); - for (let i = 0; i < 10; i++) { - const newL1Nominator = await mainContract.baseTokenGasPriceMultiplierNominator(); - if (newL1Nominator != beginL1Nominator) { - const diff = newL1Nominator - beginL1Nominator; - expect(diff).toBeLessThan(75); // as above - expect(diff).toBeGreaterThan(-75); - changedL1 = true; - break; + expect(changedL2).toBeTruthy(); + for (let i = 0; i < 10; i++) { + const newL1Nominator = await mainContract.baseTokenGasPriceMultiplierNominator(); + if (newL1Nominator != beginL1Nominator) { + const diff = newL1Nominator - beginL1Nominator; + expect(diff).toBeLessThan(75); // as above + expect(diff).toBeGreaterThan(-75); + changedL1 = true; + break; + } + await sleep(0.5); } - await sleep(0.5); - } - expect(changedL1).toBeTruthy(); + expect(changedL1).toBeTruthy(); + }); }); - test('Test gas consumption under large L1 gas price', async () => { - if (testMaster.environment().l1BatchCommitDataGeneratorMode === DataAvailabityMode.Validium) { - // We skip this test for Validium mode, since L1 gas price has little impact on the gasLimit in this mode. - return; - } + describe('Test gas price expected value', function () { + test('works', async () => { + if (testMaster.environment().l1BatchCommitDataGeneratorMode === DataAvailabityMode.Validium) { + // We skip this test for Validium mode, since L1 gas price has little impact on the gasLimit in this mode. + return; + } - // In this test we check that the server works fine when the required gasLimit is over u32::MAX. - // Under normal server behavior, the maximal gas spent on pubdata is around 120kb * 2^20 gas/byte = ~120 * 10^9 gas. + // In this test we check that the server works fine when the required gasLimit is over u32::MAX. + // Under normal server behavior, the maximal gas spent on pubdata is around 120kb * 2^20 gas/byte = ~120 * 10^9 gas. - // In this test we will set gas per pubdata byte to its maximum value, while publishing a large L1->L2 message. + // In this test we will set gas per pubdata byte to its maximum value, while publishing a large L1->L2 message. - const minimalL2GasPrice = testMaster.environment().minimalL2GasPrice; + const minimalL2GasPrice = testMaster.environment().minimalL2GasPrice; - // We want the total gas limit to be over u32::MAX, so we need the gas per pubdata to be 50k. - // - // Note, that in case, any sort of overhead is present in the l2 fair gas price calculation, the final - // gas per pubdata may be lower than 50_000. Here we assume that it is not the case, but we'll double check - // that the gasLimit is indeed over u32::MAX, which is the most important tested property. - const requiredPubdataPrice = minimalL2GasPrice * 100_000n; + // We want the total gas limit to be over u32::MAX, so we need the gas per pubdata to be 50k. + // + // Note, that in case, any sort of overhead is present in the l2 fair gas price calculation, the final + // gas per pubdata may be lower than 50_000. Here we assume that it is not the case, but we'll double check + // that the gasLimit is indeed over u32::MAX, which is the most important tested property. + const requiredPubdataPrice = minimalL2GasPrice * 100_000n; - await mainNodeSpawner.killAndSpawnMainNode({ - newL1GasPrice: requiredPubdataPrice, - newPubdataPrice: requiredPubdataPrice - }); - console.log('Node has been respawned'); - - // Wait for current batch to close so gas price is updated with the new config set above - await waitForNewL1Batch(alice); - console.log('New L1 batch has been sealed'); - - const l1Messenger = new ethers.Contract(zksync.utils.L1_MESSENGER_ADDRESS, zksync.utils.L1_MESSENGER, alice); - console.log('L1 messenger initialized'); - - // Firstly, let's test a successful transaction. - const largeData = ethers.randomBytes(90_000); - const tx = await l1Messenger.sendToL1(largeData, { type: 0 }); - console.log('L1 tx submitted'); - expect(tx.gasLimit > UINT32_MAX).toBeTruthy(); - const receipt = await tx.wait(); - console.log('L1 tx has been awaited'); - console.log(`Gas used ${receipt.gasUsed}`); - expect(receipt.gasUsed > UINT32_MAX).toBeTruthy(); - - // Let's also check that the same transaction would work as eth_call - const systemContextArtifact = getTestContract('ISystemContext'); - const systemContext = new ethers.Contract(SYSTEM_CONTEXT_ADDRESS, systemContextArtifact.abi, alice.provider); - const systemContextGasPerPubdataByte = await systemContext.gasPerPubdataByte(); - expect(systemContextGasPerPubdataByte).toEqual(MAX_GAS_PER_PUBDATA); - - const dataHash = await l1Messenger.sendToL1.staticCall(largeData, { type: 0 }); - expect(dataHash).toEqual(ethers.keccak256(largeData)); - - // Secondly, let's test an unsuccessful transaction with large refund. - - // The size of the data has increased, so the previous gas limit is not enough. - const largerData = ethers.randomBytes(91_000); - const gasToPass = receipt.gasUsed; - const unsuccessfulTx = await l1Messenger.sendToL1(largerData, { - gasLimit: gasToPass, - type: 0 - }); + await mainNodeSpawner.killAndSpawnMainNode({ + newL1GasPrice: requiredPubdataPrice, + newPubdataPrice: requiredPubdataPrice + }); + console.log('Node has been respawned'); - try { - await unsuccessfulTx.wait(); - throw new Error('The transaction should have reverted'); - } catch { - const receipt = await alice.provider.getTransactionReceipt(unsuccessfulTx.hash); - expect(gasToPass - receipt!.gasUsed > UINT32_MAX).toBeTruthy(); - } + // Wait for current batch to close so gas price is updated with the new config set above + await waitForNewL1Batch(alice); + console.log('New L1 batch has been sealed'); + + const l1Messenger = new ethers.Contract( + zksync.utils.L1_MESSENGER_ADDRESS, + zksync.utils.L1_MESSENGER, + alice + ); + console.log('L1 messenger initialized'); + + // Firstly, let's test a successful transaction. + const largeData = ethers.randomBytes(90_000); + const tx = await l1Messenger.sendToL1(largeData, { type: 0 }); + console.log('L1 tx submitted'); + expect(tx.gasLimit > UINT32_MAX).toBeTruthy(); + const receipt = await tx.wait(); + console.log('L1 tx has been awaited'); + console.log(`Gas used ${receipt.gasUsed}`); + expect(receipt.gasUsed > UINT32_MAX).toBeTruthy(); + + // Let's also check that the same transaction would work as eth_call + const systemContextArtifact = getTestContract('ISystemContext'); + const systemContext = new ethers.Contract( + SYSTEM_CONTEXT_ADDRESS, + systemContextArtifact.abi, + alice.provider + ); + const systemContextGasPerPubdataByte = await systemContext.gasPerPubdataByte(); + expect(systemContextGasPerPubdataByte).toEqual(MAX_GAS_PER_PUBDATA); + + const dataHash = await l1Messenger.sendToL1.staticCall(largeData, { type: 0 }); + expect(dataHash).toEqual(ethers.keccak256(largeData)); + + // Secondly, let's test an unsuccessful transaction with large refund. + + // The size of the data has increased, so the previous gas limit is not enough. + const largerData = ethers.randomBytes(91_000); + const gasToPass = receipt.gasUsed; + const unsuccessfulTx = await l1Messenger.sendToL1(largerData, { + gasLimit: gasToPass, + type: 0 + }); + + try { + await unsuccessfulTx.wait(); + throw new Error('The transaction should have reverted'); + } catch { + const receipt = await alice.provider.getTransactionReceipt(unsuccessfulTx.hash); + expect(gasToPass - receipt!.gasUsed > UINT32_MAX).toBeTruthy(); + } + }); }); afterAll(async () => {