Skip to content

Commit

Permalink
Implement EIP 7623: calldata cost increase (#3813)
Browse files Browse the repository at this point in the history
* common/evm/vm: implement EIP7623 calldata cost increase

* move the floorcost update and add debug log

* fix t8ntool empty requests reporting

* common: add 7623

* tx: add 7623 validator

* t8n: throw on invalid txs

* make cspell happy

---------

Co-authored-by: harkamal <[email protected]>
  • Loading branch information
jochem-brouwer and g11tech authored Dec 19, 2024
1 parent fc3c150 commit fb7e67f
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 6 deletions.
9 changes: 9 additions & 0 deletions packages/common/src/eips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/hardforks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export const hardforksDict: HardforksDict = {
prague: {
// TODO update this accordingly to the right devnet setup
//eips: [663, 3540, 3670, 4200, 4750, 5450, 6206, 7069, 7480, 7620, 7692, 7698], // This is EOF-only
eips: [2537, 2935, 6110, 7002, 7251, 7685, 7691, 7702], // This is current prague without EOF
eips: [2537, 2935, 6110, 7002, 7251, 7623, 7685, 7691, 7702], // This is current prague without EOF
},
/**
* Description: Next feature hardfork after prague, internally used for verkle testing/implementation (incomplete/experimental)
Expand Down
17 changes: 15 additions & 2 deletions packages/tx/src/capabilities/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Address,
BIGINT_0,
SECP256K1_ORDER_DIV_2,
bigIntMax,
bigIntToUnpaddedBytes,
bytesToHex,
ecrecover,
Expand Down Expand Up @@ -167,8 +168,20 @@ export function getValidationErrors(tx: LegacyTxInterface): string[] {
errors.push('Invalid Signature')
}

if (tx.getIntrinsicGas() > tx.gasLimit) {
errors.push(`gasLimit is too low. given ${tx.gasLimit}, need at least ${tx.getIntrinsicGas()}`)
let intrinsicGas = tx.getIntrinsicGas()
if (tx.common.isActivatedEIP(7623)) {
let tokens = 0
for (let i = 0; i < tx.data.length; i++) {
tokens += tx.data[i] === 0 ? 1 : 4
}
const floorCost =
tx.common.param('txGas') + tx.common.param('totalCostFloorPerToken') * BigInt(tokens)
intrinsicGas = bigIntMax(intrinsicGas, floorCost)
}
if (intrinsicGas > tx.gasLimit) {
errors.push(
`gasLimit is too low. The gasLimit is lower than the minimum gas limit of ${tx.getIntrinsicGas()}, the gas limit is: ${tx.gasLimit}`,
)
}

return errors
Expand Down
6 changes: 6 additions & 0 deletions packages/tx/src/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
29 changes: 27 additions & 2 deletions packages/vm/src/runTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
KECCAK256_NULL,
MAX_UINT64,
SECP256K1_ORDER_DIV_2,
bigIntMax,
bytesToBigInt,
bytesToHex,
bytesToUnprefixedHex,
Expand Down Expand Up @@ -249,11 +250,24 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {

// 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,
Expand Down Expand Up @@ -614,6 +628,15 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
debugGas(`tx add baseFee ${intrinsicGas} to totalGasSpent (-> ${results.totalGasSpent})`)
}

if (vm.common.isActivatedEIP(7623)) {
if (results.totalGasSpent < floorCost) {
results.totalGasSpent = floorCost
if (vm.DEBUG) {
debugGas(`tx apply floorCost ${floorCost} to totalGasSpent (-> ${results.totalGasSpent})`)
}
}
}

// Add blob gas used to result
if (isBlob4844Tx(tx)) {
results.blobGasUsed = totalblobGas
Expand All @@ -635,6 +658,8 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise<RunTxResult> {
debug(`No tx gasRefund`)
}
}


results.amountSpent = results.totalGasSpent * gasPrice

// Update sender's balance
Expand Down
125 changes: 125 additions & 0 deletions packages/vm/test/api/EIPs/eip-7623.spec.ts
Original file line number Diff line number Diff line change
@@ -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('Successfully 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)
})
})
5 changes: 4 additions & 1 deletion packages/vm/test/t8n/t8ntool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ export class TransitionTool {
for (const txData of this.txsData) {
try {
const tx = createTx(txData, { common: this.common })
if (!tx.isValid()) {
throw new Error(tx.getValidationErrors().join(', '))
}
// Set `allowNoBlobs` to `true`, since the test might not have the blob
// The 4844-tx at this should still be valid, since it has the `blobHashes` field
await builder.addTransaction(tx, { allowNoBlobs: true })
Expand Down Expand Up @@ -306,7 +309,7 @@ export class TransitionTool {
output.requests = []
for (const request of requests) {
if (request.bytes.length > 1) {
output.requests.push(bytesToHex(request.bytes))
output.requests.push(bytesToHex(request.bytes.slice(1)))
}
}
}
Expand Down

0 comments on commit fb7e67f

Please sign in to comment.