Skip to content

Commit 7e8562e

Browse files
common/evm/vm: implement EIP7623 calldata cost increase
1 parent 74a2123 commit 7e8562e

File tree

4 files changed

+163
-2
lines changed

4 files changed

+163
-2
lines changed

packages/common/src/eips.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,15 @@ export const eipsDict: EIPsDict = {
405405
*/
406406
requiredEIPs: [3540, 3541, 3670],
407407
},
408+
/**
409+
* Description : Increase calldata cost to reduce maximum block size
410+
* URL : https://github.com/ethereum/EIPs/blob/da2a86bf15044416e8eb0301c9bdb8d561feeb32/EIPS/eip-7623.md
411+
* Status : Review
412+
*/
413+
7623: {
414+
minimumHardfork: Hardfork.Chainstart,
415+
requiredEIPs: [],
416+
},
408417
/**
409418
* Description : General purpose execution layer requests
410419
* URL : https://eips.ethereum.org/EIPS/eip-7685

packages/tx/src/params.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ export const paramsTx: ParamsDict = {
4444
blobCommitmentVersionKzg: 1, // The number indicated a versioned hash is a KZG commitment
4545
},
4646
/**
47+
* Increase calldata cost to reduce maximum block size
48+
*/
49+
7623: {
50+
totalCostFloorPerToken: 10,
51+
},
52+
/**
4753
. * Set EOA account code for one transaction
4854
. */
4955
7702: {

packages/vm/src/runTx.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
KECCAK256_NULL,
1313
MAX_UINT64,
1414
SECP256K1_ORDER_DIV_2,
15+
bigIntMax,
1516
bytesToBigInt,
1617
bytesToHex,
1718
bytesToUnprefixedHex,
@@ -249,11 +250,24 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
249250

250251
// Validate gas limit against tx base fee (DataFee + TxFee + Creation Fee)
251252
const intrinsicGas = tx.getIntrinsicGas()
253+
let floorCost = BIGINT_0
254+
255+
if (vm.common.isActivatedEIP(7623)) {
256+
// Tx should at least cover the floor price for tx data
257+
let tokens = 0
258+
for (let i = 0; i < tx.data.length; i++) {
259+
tokens += tx.data[i] === 0 ? 1 : 4
260+
}
261+
floorCost =
262+
tx.common.param('txGas') + tx.common.param('totalCostFloorPerToken') * BigInt(tokens)
263+
}
264+
252265
let gasLimit = tx.gasLimit
253-
if (gasLimit < intrinsicGas) {
266+
const minGasLimit = bigIntMax(intrinsicGas, floorCost)
267+
if (gasLimit < minGasLimit) {
254268
const msg = _errorMsg(
255269
`tx gas limit ${Number(gasLimit)} is lower than the minimum gas limit of ${Number(
256-
intrinsicGas,
270+
minGasLimit,
257271
)}`,
258272
vm,
259273
block,
@@ -635,6 +649,13 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
635649
debug(`No tx gasRefund`)
636650
}
637651
}
652+
653+
if (vm.common.isActivatedEIP(7623)) {
654+
if (results.totalGasSpent < floorCost) {
655+
results.totalGasSpent = floorCost
656+
}
657+
}
658+
638659
results.amountSpent = results.totalGasSpent * gasPrice
639660

640661
// Update sender's balance
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { createBlock } from '@ethereumjs/block'
2+
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
3+
import { createLegacyTx } from '@ethereumjs/tx'
4+
import { Account, Address, createZeroAddress, hexToBytes, privateToAddress } from '@ethereumjs/util'
5+
import { assert, describe, it } from 'vitest'
6+
7+
import { createVM, runTx } from '../../../src/index.js'
8+
9+
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague })
10+
11+
const pkey = hexToBytes(`0x${'20'.repeat(32)}`)
12+
const GWEI = BigInt(1000000000)
13+
const sender = new Address(privateToAddress(pkey))
14+
15+
const coinbase = new Address(hexToBytes(`0x${'ff'.repeat(20)}`))
16+
17+
const block = createBlock(
18+
{
19+
header: {
20+
baseFeePerGas: 7,
21+
coinbase,
22+
},
23+
},
24+
{ common },
25+
)
26+
27+
const code = hexToBytes('0x60008080806001415AF100')
28+
const contractAddress = new Address(hexToBytes(`0x${'ee'.repeat(20)}`))
29+
30+
async function getVM(common: Common) {
31+
const vm = await createVM({ common })
32+
await vm.stateManager.putAccount(sender, new Account())
33+
const account = await vm.stateManager.getAccount(sender)
34+
const balance = GWEI * BigInt(21000) * BigInt(10000000)
35+
account!.balance = balance
36+
await vm.stateManager.putAccount(sender, account!)
37+
38+
await vm.stateManager.putCode(contractAddress, code)
39+
return vm
40+
}
41+
42+
describe('EIP 7623 calldata cost increase tests', () => {
43+
it('charges floor gas', async () => {
44+
const vm = await getVM(common)
45+
46+
const tx = createLegacyTx(
47+
{
48+
to: createZeroAddress(),
49+
data: new Uint8Array(100).fill(1),
50+
gasLimit: 1000000,
51+
gasPrice: 10,
52+
},
53+
{ common },
54+
).sign(pkey)
55+
56+
const result = await runTx(vm, {
57+
block,
58+
tx,
59+
skipHardForkValidation: true,
60+
})
61+
62+
const baseCost = tx.common.param('txGas')
63+
const floorCost = tx.common.param('totalCostFloorPerToken')
64+
65+
const expected = baseCost + BigInt(tx.data.length) * BigInt(4) * floorCost
66+
67+
assert.equal(result.totalGasSpent, expected)
68+
})
69+
it('rejects transactions having a gas limit below the floor gas limit', async () => {
70+
const vm = await getVM(common)
71+
72+
const tx = createLegacyTx(
73+
{
74+
to: createZeroAddress(),
75+
data: new Uint8Array(100).fill(1),
76+
gasLimit: 21000 + 100 * 4,
77+
gasPrice: 10,
78+
},
79+
{ common },
80+
).sign(pkey)
81+
try {
82+
await runTx(vm, {
83+
block,
84+
tx,
85+
skipHardForkValidation: true,
86+
})
87+
assert.fail('runTx should throw')
88+
} catch (e) {
89+
assert.ok('Succesfully failed')
90+
}
91+
})
92+
it('correctly charges execution gas instead of floor gas when execution gas exceeds the floor gas', async () => {
93+
const vm = await getVM(common)
94+
const to = createZeroAddress()
95+
96+
// Store 1 in slot 1
97+
await vm.stateManager.putCode(to, hexToBytes('0x6001600155'))
98+
99+
const tx = createLegacyTx(
100+
{
101+
to: createZeroAddress(),
102+
data: new Uint8Array(100).fill(1),
103+
gasLimit: 1000000,
104+
gasPrice: 10,
105+
},
106+
{ common },
107+
).sign(pkey)
108+
109+
const result = await runTx(vm, {
110+
block,
111+
tx,
112+
skipHardForkValidation: true,
113+
})
114+
115+
const baseCost = tx.common.param('txGas')
116+
117+
const expected =
118+
baseCost +
119+
BigInt(tx.data.length) * tx.common.param('txDataNonZeroGas') +
120+
BigInt(2 * 3) +
121+
BigInt(22_100)
122+
123+
assert.equal(result.totalGasSpent, expected)
124+
})
125+
})

0 commit comments

Comments
 (0)