Skip to content

Commit d614ca4

Browse files
committed
edits intrinsic gas cost precheck
Signed-off-by: Konstantina Blazhukova <[email protected]>
1 parent 2e9648a commit d614ca4

File tree

3 files changed

+71
-21
lines changed

3 files changed

+71
-21
lines changed

packages/relay/src/lib/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ export default {
9696
TX_DEFAULT_GAS_DEFAULT: 400_000,
9797
TX_CREATE_EXTRA: 32_000,
9898
TX_DATA_ZERO_COST: 4,
99+
100+
// EIP-7623: Calldata floor pricing constants
101+
TOTAL_COST_FLOOR_PER_TOKEN: 10,
102+
103+
// EIP-3860: Initcode cost for contract creation
104+
INITCODE_WORD_COST: 2,
105+
106+
// EIP-7702: Authorization list cost for type 4 transactions
107+
PER_EMPTY_ACCOUNT_COST: 25_000,
99108
BALANCES_UPDATE_INTERVAL: 900, // 15 minutes
100109
MAX_MIRROR_NODE_PAGINATION: 20,
101110
MIRROR_NODE_QUERY_LIMIT: 100,

packages/relay/src/lib/precheck.ts

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
44
import { ethers, Transaction } from 'ethers';
5+
import { Logger } from 'pino';
56

67
import { prepend0x } from '../formatters';
78
import { MirrorNodeClient } from './clients';
@@ -205,7 +206,7 @@ export class Precheck {
205206
*/
206207
gasLimit(tx: Transaction): void {
207208
const gasLimit = Number(tx.gasLimit);
208-
const intrinsicGasCost = Precheck.transactionIntrinsicGasCost(tx.data);
209+
const intrinsicGasCost = Precheck.transactionIntrinsicGasCost(tx);
209210

210211
if (gasLimit > constants.MAX_TRANSACTION_FEE_THRESHOLD) {
211212
throw predefined.GAS_LIMIT_TOO_HIGH(gasLimit, constants.MAX_TRANSACTION_FEE_THRESHOLD);
@@ -215,30 +216,67 @@ export class Precheck {
215216
}
216217

217218
/**
218-
* Calculates the intrinsic gas cost based on the number of bytes in the data field.
219-
* Using a loop that goes through every two characters in the string it counts the zero and non-zero bytes.
220-
* Every two characters that are packed together and are both zero counts towards zero bytes.
221-
* @param data - The data with the bytes to be calculated
222-
* @returns The intrinsic gas cost.
223-
* @private
219+
* Calculates the intrinsic gas cost based on EIP-7623 floor pricing rules.
220+
*
221+
* The intrinsic gas is calculated as:
222+
* max(standardIntrinsicGas, floorPrice)
223+
*
224+
* Where:
225+
* - standardIntrinsicGas = TX_BASE_COST + calldata cost + contract creation cost + auth list cost
226+
* - floorPrice = TX_BASE_COST + TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata
227+
* - tokens_in_calldata = zero_bytes + non_zero_bytes * 4
228+
*
229+
* @see https://eips.ethereum.org/EIPS/eip-7623
230+
* @see https://eips.ethereum.org/EIPS/eip-7702
231+
* @see https://eips.ethereum.org/EIPS/eip-3860
232+
*
233+
* @param tx - The transaction object
234+
* @returns The intrinsic gas cost (maximum of standard cost and floor price).
224235
*/
225-
public static transactionIntrinsicGasCost(data: string): number {
226-
const trimmedData = data.replace('0x', '');
227-
228-
let zeros = 0;
229-
let nonZeros = 0;
230-
for (let index = 0; index < trimmedData.length; index += 2) {
231-
const bytes = trimmedData[index] + trimmedData[index + 1];
232-
if (bytes === '00') {
233-
zeros++;
236+
public static transactionIntrinsicGasCost(tx: Transaction): number {
237+
const calldata = tx.data.replace('0x', '');
238+
239+
// Count zero and non-zero bytes in calldata
240+
let zeroBytes = 0;
241+
let nonZeroBytes = 0;
242+
for (let index = 0; index < calldata.length; index += 2) {
243+
const byte = calldata[index] + calldata[index + 1];
244+
if (byte === '00') {
245+
zeroBytes++;
234246
} else {
235-
nonZeros++;
247+
nonZeroBytes++;
236248
}
237249
}
238250

239-
return (
240-
constants.TX_BASE_COST + constants.TX_DATA_ZERO_COST * zeros + constants.ISTANBUL_TX_DATA_NON_ZERO_COST * nonZeros
241-
);
251+
// EIP-7623: tokens_in_calldata = zero_bytes + non_zero_bytes * 4
252+
const tokensInCalldata = zeroBytes + nonZeroBytes * 4;
253+
254+
// Standard intrinsic gas cost (calldata pricing: 4 gas per zero byte, 16 gas per non-zero byte)
255+
let standardIntrinsicGas =
256+
constants.TX_BASE_COST +
257+
constants.TX_DATA_ZERO_COST * zeroBytes +
258+
constants.ISTANBUL_TX_DATA_NON_ZERO_COST * nonZeroBytes;
259+
260+
// EIP-3860: Add contract creation cost if tx.to is null (contract deployment)
261+
const isContractCreation = tx.to === null || tx.to === undefined;
262+
if (isContractCreation) {
263+
const calldataLengthInBytes = calldata.length / 2;
264+
const words = Math.ceil(calldataLengthInBytes / 32);
265+
standardIntrinsicGas += constants.TX_CREATE_EXTRA + constants.INITCODE_WORD_COST * words;
266+
}
267+
268+
// EIP-7702: Add authorization list cost for type 4 transactions
269+
// Note: ethers.js Transaction type may not have authorizationList property yet
270+
const authorizationList = (tx as any).authorizationList;
271+
if (tx.type === 4 && authorizationList && Array.isArray(authorizationList)) {
272+
standardIntrinsicGas += constants.PER_EMPTY_ACCOUNT_COST * authorizationList.length;
273+
}
274+
275+
// EIP-7623: Floor price for calldata-heavy transactions
276+
const floorPrice = constants.TX_BASE_COST + constants.TOTAL_COST_FLOOR_PER_TOKEN * tokensInCalldata;
277+
278+
// Return the maximum of standard intrinsic gas and floor price
279+
return Math.max(standardIntrinsicGas, floorPrice);
242280
}
243281

244282
/**

packages/relay/src/lib/services/ethService/contractService/ContractService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22

33
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
4+
import { Transaction } from 'ethers';
45
import { Logger } from 'pino';
56

67
import {
@@ -607,7 +608,9 @@ export class ContractService implements IContractService {
607608
return predefined.CONTRACT_REVERT(error.detail, error.data);
608609
}
609610
this.logger.warn(`Returning predefined gas for contract creation: ${gasTxBaseCost}`);
610-
return numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!));
611+
// Create a minimal transaction-like object for intrinsic gas calculation
612+
const txForGasCalc = { data: transaction.data!, to: transaction.to ?? null } as Transaction;
613+
return numberTo0x(Precheck.transactionIntrinsicGasCost(txForGasCalc));
611614
} else if (isContractCall) {
612615
this.logger.warn(`Returning predefined gas for contract call: ${contractCallAverageGas}`);
613616
return contractCallAverageGas;

0 commit comments

Comments
 (0)