From 7e8562e335ffd9500b50e641c7d19e9119a779d4 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 9 Dec 2024 21:03:34 +0100 Subject: [PATCH] common/evm/vm: implement EIP7623 calldata cost increase --- packages/common/src/eips.ts | 9 ++ packages/tx/src/params.ts | 6 + packages/vm/src/runTx.ts | 25 ++++- packages/vm/test/api/EIPs/eip-7623.spec.ts | 125 +++++++++++++++++++++ 4 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 packages/vm/test/api/EIPs/eip-7623.spec.ts diff --git a/packages/common/src/eips.ts b/packages/common/src/eips.ts index 6c43bfa341..1c6e31aab0 100644 --- a/packages/common/src/eips.ts +++ b/packages/common/src/eips.ts @@ -405,6 +405,15 @@ export const eipsDict: EIPsDict = { */ requiredEIPs: [3540, 3541, 3670], }, + /** + * Description : Increase calldata cost to reduce maximum block size + * URL : https://github.com/ethereum/EIPs/blob/da2a86bf15044416e8eb0301c9bdb8d561feeb32/EIPS/eip-7623.md + * Status : Review + */ + 7623: { + minimumHardfork: Hardfork.Chainstart, + requiredEIPs: [], + }, /** * Description : General purpose execution layer requests * URL : https://eips.ethereum.org/EIPS/eip-7685 diff --git a/packages/tx/src/params.ts b/packages/tx/src/params.ts index 66ce2302bb..d5de087267 100644 --- a/packages/tx/src/params.ts +++ b/packages/tx/src/params.ts @@ -44,6 +44,12 @@ export const paramsTx: ParamsDict = { blobCommitmentVersionKzg: 1, // The number indicated a versioned hash is a KZG commitment }, /** + * Increase calldata cost to reduce maximum block size + */ + 7623: { + totalCostFloorPerToken: 10, + }, + /** . * Set EOA account code for one transaction . */ 7702: { diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index 3697f810ad..125ab5f92d 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -12,6 +12,7 @@ import { KECCAK256_NULL, MAX_UINT64, SECP256K1_ORDER_DIV_2, + bigIntMax, bytesToBigInt, bytesToHex, bytesToUnprefixedHex, @@ -249,11 +250,24 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { // Validate gas limit against tx base fee (DataFee + TxFee + Creation Fee) const intrinsicGas = tx.getIntrinsicGas() + let floorCost = BIGINT_0 + + if (vm.common.isActivatedEIP(7623)) { + // Tx should at least cover the floor price for tx data + let tokens = 0 + for (let i = 0; i < tx.data.length; i++) { + tokens += tx.data[i] === 0 ? 1 : 4 + } + floorCost = + tx.common.param('txGas') + tx.common.param('totalCostFloorPerToken') * BigInt(tokens) + } + let gasLimit = tx.gasLimit - if (gasLimit < intrinsicGas) { + const minGasLimit = bigIntMax(intrinsicGas, floorCost) + if (gasLimit < minGasLimit) { const msg = _errorMsg( `tx gas limit ${Number(gasLimit)} is lower than the minimum gas limit of ${Number( - intrinsicGas, + minGasLimit, )}`, vm, block, @@ -635,6 +649,13 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { debug(`No tx gasRefund`) } } + + if (vm.common.isActivatedEIP(7623)) { + if (results.totalGasSpent < floorCost) { + results.totalGasSpent = floorCost + } + } + results.amountSpent = results.totalGasSpent * gasPrice // Update sender's balance diff --git a/packages/vm/test/api/EIPs/eip-7623.spec.ts b/packages/vm/test/api/EIPs/eip-7623.spec.ts new file mode 100644 index 0000000000..d0567d398f --- /dev/null +++ b/packages/vm/test/api/EIPs/eip-7623.spec.ts @@ -0,0 +1,125 @@ +import { createBlock } from '@ethereumjs/block' +import { Common, Hardfork, Mainnet } from '@ethereumjs/common' +import { createLegacyTx } from '@ethereumjs/tx' +import { Account, Address, createZeroAddress, hexToBytes, privateToAddress } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { createVM, runTx } from '../../../src/index.js' + +const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague }) + +const pkey = hexToBytes(`0x${'20'.repeat(32)}`) +const GWEI = BigInt(1000000000) +const sender = new Address(privateToAddress(pkey)) + +const coinbase = new Address(hexToBytes(`0x${'ff'.repeat(20)}`)) + +const block = createBlock( + { + header: { + baseFeePerGas: 7, + coinbase, + }, + }, + { common }, +) + +const code = hexToBytes('0x60008080806001415AF100') +const contractAddress = new Address(hexToBytes(`0x${'ee'.repeat(20)}`)) + +async function getVM(common: Common) { + const vm = await createVM({ common }) + await vm.stateManager.putAccount(sender, new Account()) + const account = await vm.stateManager.getAccount(sender) + const balance = GWEI * BigInt(21000) * BigInt(10000000) + account!.balance = balance + await vm.stateManager.putAccount(sender, account!) + + await vm.stateManager.putCode(contractAddress, code) + return vm +} + +describe('EIP 7623 calldata cost increase tests', () => { + it('charges floor gas', async () => { + const vm = await getVM(common) + + const tx = createLegacyTx( + { + to: createZeroAddress(), + data: new Uint8Array(100).fill(1), + gasLimit: 1000000, + gasPrice: 10, + }, + { common }, + ).sign(pkey) + + const result = await runTx(vm, { + block, + tx, + skipHardForkValidation: true, + }) + + const baseCost = tx.common.param('txGas') + const floorCost = tx.common.param('totalCostFloorPerToken') + + const expected = baseCost + BigInt(tx.data.length) * BigInt(4) * floorCost + + assert.equal(result.totalGasSpent, expected) + }) + it('rejects transactions having a gas limit below the floor gas limit', async () => { + const vm = await getVM(common) + + const tx = createLegacyTx( + { + to: createZeroAddress(), + data: new Uint8Array(100).fill(1), + gasLimit: 21000 + 100 * 4, + gasPrice: 10, + }, + { common }, + ).sign(pkey) + try { + await runTx(vm, { + block, + tx, + skipHardForkValidation: true, + }) + assert.fail('runTx should throw') + } catch (e) { + assert.ok('Succesfully failed') + } + }) + it('correctly charges execution gas instead of floor gas when execution gas exceeds the floor gas', async () => { + const vm = await getVM(common) + const to = createZeroAddress() + + // Store 1 in slot 1 + await vm.stateManager.putCode(to, hexToBytes('0x6001600155')) + + const tx = createLegacyTx( + { + to: createZeroAddress(), + data: new Uint8Array(100).fill(1), + gasLimit: 1000000, + gasPrice: 10, + }, + { common }, + ).sign(pkey) + + const result = await runTx(vm, { + block, + tx, + skipHardForkValidation: true, + }) + + const baseCost = tx.common.param('txGas') + + const expected = + baseCost + + BigInt(tx.data.length) * tx.common.param('txDataNonZeroGas') + + BigInt(2 * 3) + + BigInt(22_100) + + assert.equal(result.totalGasSpent, expected) + }) +})