@feelyourprotocol/vm 8141.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +583 -0
  3. package/dist/cjs/bloom/index.d.ts +29 -0
  4. package/dist/cjs/bloom/index.d.ts.map +1 -0
  5. package/dist/cjs/bloom/index.js +76 -0
  6. package/dist/cjs/bloom/index.js.map +1 -0
  7. package/dist/cjs/buildBlock.d.ts +118 -0
  8. package/dist/cjs/buildBlock.d.ts.map +1 -0
  9. package/dist/cjs/buildBlock.js +363 -0
  10. package/dist/cjs/buildBlock.js.map +1 -0
  11. package/dist/cjs/constructors.d.ts +9 -0
  12. package/dist/cjs/constructors.d.ts.map +1 -0
  13. package/dist/cjs/constructors.js +75 -0
  14. package/dist/cjs/constructors.js.map +1 -0
  15. package/dist/cjs/emitEVMProfile.d.ts +9 -0
  16. package/dist/cjs/emitEVMProfile.d.ts.map +1 -0
  17. package/dist/cjs/emitEVMProfile.js +130 -0
  18. package/dist/cjs/emitEVMProfile.js.map +1 -0
  19. package/dist/cjs/index.d.ts +11 -0
  20. package/dist/cjs/index.d.ts.map +1 -0
  21. package/dist/cjs/index.js +36 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/package.json +3 -0
  24. package/dist/cjs/params.d.ts +3 -0
  25. package/dist/cjs/params.d.ts.map +1 -0
  26. package/dist/cjs/params.js +105 -0
  27. package/dist/cjs/params.js.map +1 -0
  28. package/dist/cjs/requests.d.ts +11 -0
  29. package/dist/cjs/requests.d.ts.map +1 -0
  30. package/dist/cjs/requests.js +208 -0
  31. package/dist/cjs/requests.js.map +1 -0
  32. package/dist/cjs/runBlock.d.ts +35 -0
  33. package/dist/cjs/runBlock.d.ts.map +1 -0
  34. package/dist/cjs/runBlock.js +797 -0
  35. package/dist/cjs/runBlock.js.map +1 -0
  36. package/dist/cjs/runFrameTx.d.ts +18 -0
  37. package/dist/cjs/runFrameTx.d.ts.map +1 -0
  38. package/dist/cjs/runFrameTx.js +313 -0
  39. package/dist/cjs/runFrameTx.js.map +1 -0
  40. package/dist/cjs/runTx.d.ts +18 -0
  41. package/dist/cjs/runTx.d.ts.map +1 -0
  42. package/dist/cjs/runTx.js +900 -0
  43. package/dist/cjs/runTx.js.map +1 -0
  44. package/dist/cjs/types.d.ts +452 -0
  45. package/dist/cjs/types.d.ts.map +1 -0
  46. package/dist/cjs/types.js +3 -0
  47. package/dist/cjs/types.js.map +1 -0
  48. package/dist/cjs/vm.d.ts +75 -0
  49. package/dist/cjs/vm.d.ts.map +1 -0
  50. package/dist/cjs/vm.js +111 -0
  51. package/dist/cjs/vm.js.map +1 -0
  52. package/dist/esm/bloom/index.d.ts +29 -0
  53. package/dist/esm/bloom/index.d.ts.map +1 -0
  54. package/dist/esm/bloom/index.js +72 -0
  55. package/dist/esm/bloom/index.js.map +1 -0
  56. package/dist/esm/buildBlock.d.ts +118 -0
  57. package/dist/esm/buildBlock.d.ts.map +1 -0
  58. package/dist/esm/buildBlock.js +358 -0
  59. package/dist/esm/buildBlock.js.map +1 -0
  60. package/dist/esm/constructors.d.ts +9 -0
  61. package/dist/esm/constructors.d.ts.map +1 -0
  62. package/dist/esm/constructors.js +72 -0
  63. package/dist/esm/constructors.js.map +1 -0
  64. package/dist/esm/emitEVMProfile.d.ts +9 -0
  65. package/dist/esm/emitEVMProfile.d.ts.map +1 -0
  66. package/dist/esm/emitEVMProfile.js +127 -0
  67. package/dist/esm/emitEVMProfile.js.map +1 -0
  68. package/dist/esm/index.d.ts +11 -0
  69. package/dist/esm/index.d.ts.map +1 -0
  70. package/dist/esm/index.js +11 -0
  71. package/dist/esm/index.js.map +1 -0
  72. package/dist/esm/package.json +3 -0
  73. package/dist/esm/params.d.ts +3 -0
  74. package/dist/esm/params.d.ts.map +1 -0
  75. package/dist/esm/params.js +102 -0
  76. package/dist/esm/params.js.map +1 -0
  77. package/dist/esm/requests.d.ts +11 -0
  78. package/dist/esm/requests.d.ts.map +1 -0
  79. package/dist/esm/requests.js +204 -0
  80. package/dist/esm/requests.js.map +1 -0
  81. package/dist/esm/runBlock.d.ts +35 -0
  82. package/dist/esm/runBlock.d.ts.map +1 -0
  83. package/dist/esm/runBlock.js +790 -0
  84. package/dist/esm/runBlock.js.map +1 -0
  85. package/dist/esm/runFrameTx.d.ts +18 -0
  86. package/dist/esm/runFrameTx.d.ts.map +1 -0
  87. package/dist/esm/runFrameTx.js +310 -0
  88. package/dist/esm/runFrameTx.js.map +1 -0
  89. package/dist/esm/runTx.d.ts +18 -0
  90. package/dist/esm/runTx.d.ts.map +1 -0
  91. package/dist/esm/runTx.js +896 -0
  92. package/dist/esm/runTx.js.map +1 -0
  93. package/dist/esm/types.d.ts +452 -0
  94. package/dist/esm/types.d.ts.map +1 -0
  95. package/dist/esm/types.js +2 -0
  96. package/dist/esm/types.js.map +1 -0
  97. package/dist/esm/vm.d.ts +75 -0
  98. package/dist/esm/vm.d.ts.map +1 -0
  99. package/dist/esm/vm.js +107 -0
  100. package/dist/esm/vm.js.map +1 -0
  101. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  102. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  103. package/package.json +117 -0
  104. package/src/bloom/index.ts +83 -0
  105. package/src/buildBlock.ts +470 -0
  106. package/src/constructors.ts +91 -0
  107. package/src/emitEVMProfile.ts +151 -0
  108. package/src/index.ts +10 -0
  109. package/src/params.ts +104 -0
  110. package/src/requests.ts +293 -0
  111. package/src/runBlock.ts +1022 -0
  112. package/src/runFrameTx.ts +411 -0
  113. package/src/runTx.ts +1203 -0
  114. package/src/types.ts +511 -0
  115. package/src/vm.ts +147 -0
@@ -0,0 +1,896 @@
1
+ import { cliqueSigner, createBlockHeader } from '@feelyourprotocol/block';
2
+ import { ConsensusType, Hardfork } from '@feelyourprotocol/common';
3
+ import { BinaryTreeAccessWitness, createEIP7708SelfdestructLog, } from '@feelyourprotocol/evm';
4
+ import { Capability, isBlob4844Tx, isFrameEIP8141Tx } from '@feelyourprotocol/tx';
5
+ import { Account, Address, BIGINT_0, BIGINT_1, EthereumJSErrorWithoutCode, KECCAK256_NULL, MAX_UINT64, SECP256K1_ORDER_DIV_2, bigIntMax, bytesToBigInt, bytesToHex, bytesToUnprefixedHex, concatBytes, eoaCode7702RecoverAuthority, equalsBytes, hexToBytes, short, } from '@feelyourprotocol/util';
6
+ import debugDefault from 'debug';
7
+ import { Bloom } from "./bloom/index.js";
8
+ import { emitEVMProfile } from "./emitEVMProfile.js";
9
+ import { runFrameTransaction } from "./runFrameTx.js";
10
+ const debug = debugDefault('vm:tx');
11
+ const debugGas = debugDefault('vm:tx:gas');
12
+ const DEFAULT_HEADER = createBlockHeader();
13
+ let enableProfiler = false;
14
+ const initLabel = 'EVM journal init, address/slot warming, fee validation';
15
+ const balanceNonceLabel = 'Balance/Nonce checks and update';
16
+ const executionLabel = 'Execution';
17
+ const logsGasBalanceLabel = 'Logs, gas usage, account/miner balances';
18
+ const accountsCleanUpLabel = 'Accounts clean up';
19
+ const accessListLabel = 'Access list label';
20
+ const journalCacheCleanUpLabel = 'Journal/cache cleanup';
21
+ const receiptsLabel = 'Receipts';
22
+ const entireTxLabel = 'Entire tx';
23
+ // EIP-7702 flag: if contract code starts with these 3 bytes, it is a 7702-delegated EOA
24
+ const DELEGATION_7702_FLAG = new Uint8Array([0xef, 0x01, 0x00]);
25
+ /**
26
+ * Process EIP-7702 authorization list tuples.
27
+ * Sets delegation code for authorized accounts and calculates gas refunds.
28
+ *
29
+ * @param vm - The VM instance
30
+ * @param tx - The transaction (must support EIP7702EOACode capability)
31
+ * @param caller - The transaction sender address
32
+ * @param initialGasRefund - The current gas refund amount
33
+ * @returns The updated gas refund amount
34
+ */
35
+ async function processAuthorizationList(vm, tx, caller, initialGasRefund) {
36
+ let gasRefund = initialGasRefund;
37
+ const authorizationList = tx.authorizationList;
38
+ for (let i = 0; i < authorizationList.length; i++) {
39
+ const data = authorizationList[i];
40
+ // Validate chain ID
41
+ const chainId = data[0];
42
+ const chainIdBN = bytesToBigInt(chainId);
43
+ if (chainIdBN !== BIGINT_0 && chainIdBN !== vm.common.chainId()) {
44
+ continue;
45
+ }
46
+ // Validate nonce bounds
47
+ const authorityNonce = data[2];
48
+ if (bytesToBigInt(authorityNonce) >= MAX_UINT64) {
49
+ // Authority nonce >= 2^64 - 1. Bumping this nonce by one will not make this fit in an uint64.
50
+ // EIPs PR: https://github.com/ethereum/EIPs/pull/8938
51
+ continue;
52
+ }
53
+ // Validate signature malleability (s value)
54
+ const s = data[5];
55
+ if (bytesToBigInt(s) > SECP256K1_ORDER_DIV_2) {
56
+ // Malleability protection to avoid "flipping" a valid signature
57
+ continue;
58
+ }
59
+ // Validate yParity
60
+ const yParity = bytesToBigInt(data[3]);
61
+ if (yParity > BIGINT_1) {
62
+ continue;
63
+ }
64
+ // Recover authority address from signature
65
+ let authority;
66
+ try {
67
+ authority = eoaCode7702RecoverAuthority(data);
68
+ }
69
+ catch {
70
+ // Invalid signature
71
+ continue;
72
+ }
73
+ const accountMaybeUndefined = await vm.stateManager.getAccount(authority);
74
+ const accountExists = accountMaybeUndefined !== undefined;
75
+ const account = accountMaybeUndefined ?? new Account();
76
+ // Add authority address to warm addresses
77
+ vm.evm.journal.addAlwaysWarmAddress(authority.toString());
78
+ // EIP-7928: Add authority address to BAL (even if authorization fails later,
79
+ // the account was accessed to check nonce/code)
80
+ if (vm.common.isActivatedEIP(7928)) {
81
+ vm.evm.blockLevelAccessList.addAddress(authority.toString());
82
+ }
83
+ // Skip if account is a "normal" contract (not 7702-delegated)
84
+ if (account.isContract()) {
85
+ const code = await vm.stateManager.getCode(authority);
86
+ if (!equalsBytes(code.slice(0, 3), DELEGATION_7702_FLAG)) {
87
+ continue;
88
+ }
89
+ }
90
+ // Nonce validation
91
+ if (caller.toString() === authority.toString()) {
92
+ // Edge case: caller is the authority (self-signing delegation)
93
+ // Virtually bump the account nonce by one for comparison
94
+ if (account.nonce + BIGINT_1 !== bytesToBigInt(authorityNonce)) {
95
+ continue;
96
+ }
97
+ }
98
+ else if (account.nonce !== bytesToBigInt(authorityNonce)) {
99
+ continue;
100
+ }
101
+ // Calculate gas refund for existing accounts
102
+ if (accountExists) {
103
+ const refund = tx.common.param('perEmptyAccountCost') - tx.common.param('perAuthBaseGas');
104
+ gasRefund += refund;
105
+ }
106
+ // Update account nonce and store
107
+ account.nonce++;
108
+ await vm.evm.journal.putAccount(authority, account);
109
+ if (vm.common.isActivatedEIP(7928)) {
110
+ vm.evm.blockLevelAccessList.addNonceChange(authority.toString(), account.nonce, vm.evm.blockLevelAccessList.blockAccessIndex);
111
+ }
112
+ // Set delegation code
113
+ const address = data[1];
114
+ // Get current code before modifying (needed for BAL tracking)
115
+ const currentCode = vm.common.isActivatedEIP(7928)
116
+ ? await vm.stateManager.getCode(authority)
117
+ : undefined;
118
+ if (equalsBytes(address, new Uint8Array(20))) {
119
+ // Special case: clear delegation when delegating to zero address
120
+ // See EIP PR: https://github.com/ethereum/EIPs/pull/8929
121
+ await vm.stateManager.putCode(authority, new Uint8Array());
122
+ if (vm.common.isActivatedEIP(7928)) {
123
+ vm.evm.blockLevelAccessList.addCodeChange(authority.toString(), new Uint8Array(), vm.evm.blockLevelAccessList.blockAccessIndex, currentCode);
124
+ }
125
+ }
126
+ else {
127
+ const addressCode = concatBytes(DELEGATION_7702_FLAG, address);
128
+ await vm.stateManager.putCode(authority, addressCode);
129
+ if (vm.common.isActivatedEIP(7928)) {
130
+ vm.evm.blockLevelAccessList.addCodeChange(authority.toString(), addressCode, vm.evm.blockLevelAccessList.blockAccessIndex, currentCode);
131
+ }
132
+ }
133
+ }
134
+ return gasRefund;
135
+ }
136
+ /**
137
+ * Process selfdestruct cleanup for accounts marked for destruction.
138
+ * Handles EIP-6780 restrictions (only delete contracts created in same tx).
139
+ *
140
+ * @param vm - The VM instance
141
+ * @param results - The execution results containing selfdestruct list
142
+ */
143
+ async function processSelfdestructs(vm, results) {
144
+ if (results.execResult.selfdestruct === undefined) {
145
+ return;
146
+ }
147
+ const destroyedForBAL = new Set();
148
+ const finalizationLogs = [];
149
+ const sortedSelfdestructs = [...results.execResult.selfdestruct.entries()].sort(([a], [b]) => a.localeCompare(b));
150
+ for (const [addressToSelfdestructHex] of sortedSelfdestructs) {
151
+ const address = new Address(hexToBytes(addressToSelfdestructHex));
152
+ // EIP-6780: Only delete contracts created in the same transaction
153
+ if (vm.common.isActivatedEIP(6780)) {
154
+ if (!results.execResult.createdAddresses.has(address.toString())) {
155
+ continue;
156
+ }
157
+ }
158
+ if (vm.common.isActivatedEIP(7708)) {
159
+ const account = await vm.stateManager.getAccount(address);
160
+ const finalizationBalance = account?.balance ?? BIGINT_0;
161
+ if (finalizationBalance > BIGINT_0) {
162
+ finalizationLogs.push(createEIP7708SelfdestructLog(address, finalizationBalance));
163
+ }
164
+ }
165
+ await vm.evm.journal.deleteAccount(address);
166
+ destroyedForBAL.add(address.toString());
167
+ if (vm.DEBUG) {
168
+ debug(`tx selfdestruct on address=${address}`);
169
+ }
170
+ }
171
+ if (finalizationLogs.length > 0) {
172
+ results.execResult.logs = [...(results.execResult.logs ?? []), ...finalizationLogs];
173
+ }
174
+ if (destroyedForBAL.size > 0 && vm.common.isActivatedEIP(7928)) {
175
+ vm.evm.blockLevelAccessList.cleanupSelfdestructed([...destroyedForBAL]);
176
+ }
177
+ }
178
+ /**
179
+ * Build the access list result from the journal's tracked accesses.
180
+ * Converts the internal Map format to the standard AccessList format.
181
+ *
182
+ * @param vm - The VM instance
183
+ * @returns The formatted access list
184
+ */
185
+ function buildAccessListResult(vm) {
186
+ const accessList = [];
187
+ for (const [address, storageSet] of vm.evm.journal.accessList) {
188
+ const item = {
189
+ address: `0x${address}`,
190
+ storageKeys: [],
191
+ };
192
+ for (const slot of storageSet) {
193
+ item.storageKeys.push(`0x${slot}`);
194
+ }
195
+ accessList.push(item);
196
+ }
197
+ return accessList;
198
+ }
199
+ /**
200
+ * Update the miner's account balance with the transaction fee.
201
+ * Handles both EIP-1559 (priority fee only) and legacy (full gas price) fee models.
202
+ *
203
+ * @param vm - The VM instance
204
+ * @param state - The state manager
205
+ * @param block - The block (optional)
206
+ * @param results - The transaction results to update with minerValue
207
+ * @param inclusionFeePerGas - The priority fee per gas (for EIP-1559)
208
+ */
209
+ async function updateMinerBalance(vm, state, block, results, inclusionFeePerGas) {
210
+ // Determine miner address based on consensus type
211
+ let miner;
212
+ if (vm.common.consensusType() === ConsensusType.ProofOfAuthority) {
213
+ miner = cliqueSigner(block?.header ?? DEFAULT_HEADER);
214
+ }
215
+ else {
216
+ miner = block?.header.coinbase ?? DEFAULT_HEADER.coinbase;
217
+ }
218
+ // Get or create miner account
219
+ let minerAccount = await state.getAccount(miner);
220
+ if (minerAccount === undefined) {
221
+ minerAccount = new Account();
222
+ }
223
+ // Calculate miner value: priority fee for EIP-1559, full amount for legacy
224
+ results.minerValue = vm.common.isActivatedEIP(1559)
225
+ ? results.totalGasSpent * inclusionFeePerGas
226
+ : results.amountSpent;
227
+ const minerOriginalBalance = minerAccount.balance;
228
+ minerAccount.balance += results.minerValue;
229
+ if (vm.common.isActivatedEIP(7928)) {
230
+ if (results.minerValue !== BIGINT_0) {
231
+ vm.evm.blockLevelAccessList.addBalanceChange(miner.toString(), minerAccount.balance, vm.evm.blockLevelAccessList.blockAccessIndex, minerOriginalBalance);
232
+ }
233
+ else {
234
+ // EIP-7928: If the COINBASE reward is zero, the COINBASE address
235
+ // MUST be included as a read (address only, no balance change)
236
+ vm.evm.blockLevelAccessList.addAddress(miner.toString());
237
+ }
238
+ }
239
+ // Store updated miner account
240
+ // Note: If balance remains zero, account is marked as "touched" and may be
241
+ // removed during cleanup for forks >= SpuriousDragon
242
+ await vm.evm.journal.putAccount(miner, minerAccount);
243
+ if (vm.DEBUG) {
244
+ debug(`tx update miner account (${miner}) balance (-> ${minerAccount.balance})`);
245
+ }
246
+ }
247
+ /**
248
+ * @ignore
249
+ */
250
+ export async function runTx(vm, opts) {
251
+ if (vm['_opts'].profilerOpts?.reportAfterTx === true) {
252
+ enableProfiler = true;
253
+ }
254
+ if (enableProfiler) {
255
+ const title = `Profiler run - Tx ${bytesToHex(opts.tx.hash())}`;
256
+ // eslint-disable-next-line no-console
257
+ console.log(title);
258
+ // eslint-disable-next-line no-console
259
+ console.time(initLabel);
260
+ // eslint-disable-next-line no-console
261
+ console.time(entireTxLabel);
262
+ }
263
+ if (opts.skipHardForkValidation !== true && opts.block !== undefined) {
264
+ // If block and tx don't have a same hardfork, set tx hardfork to block
265
+ if (opts.tx.common.hardfork() !== opts.block.common.hardfork()) {
266
+ opts.tx.common.setHardfork(opts.block.common.hardfork());
267
+ }
268
+ if (opts.block.common.hardfork() !== vm.common.hardfork()) {
269
+ // Block and VM's hardfork should match as well
270
+ const msg = _errorMsg('block has a different hardfork than the vm', vm, opts.block, opts.tx);
271
+ throw EthereumJSErrorWithoutCode(msg);
272
+ }
273
+ }
274
+ const gasLimit = opts.block?.header.gasLimit ?? DEFAULT_HEADER.gasLimit;
275
+ if (opts.skipBlockGasLimitValidation !== true && gasLimit < opts.tx.gasLimit) {
276
+ const msg = _errorMsg('tx has a higher gas limit than the block', vm, opts.block, opts.tx);
277
+ throw EthereumJSErrorWithoutCode(msg);
278
+ }
279
+ // Ensure we start with a clear warmed accounts Map
280
+ await vm.evm.journal.cleanup();
281
+ if (opts.reportAccessList === true) {
282
+ vm.evm.journal.startReportingAccessList();
283
+ }
284
+ if (opts.reportPreimages === true) {
285
+ vm.evm.journal.startReportingPreimages();
286
+ }
287
+ await vm.evm.journal.checkpoint();
288
+ if (vm.DEBUG) {
289
+ debug('-'.repeat(100));
290
+ debug(`tx checkpoint`);
291
+ }
292
+ // Typed transaction specific setup tasks
293
+ if (opts.tx.supports(Capability.EIP2718TypedTransaction) && vm.common.isActivatedEIP(2718)) {
294
+ // Is it an Access List transaction?
295
+ if (!vm.common.isActivatedEIP(2930)) {
296
+ await vm.evm.journal.revert();
297
+ const msg = _errorMsg('Cannot run transaction: EIP 2930 is not activated.', vm, opts.block, opts.tx);
298
+ throw EthereumJSErrorWithoutCode(msg);
299
+ }
300
+ if (opts.tx.supports(Capability.EIP1559FeeMarket) && !vm.common.isActivatedEIP(1559)) {
301
+ await vm.evm.journal.revert();
302
+ const msg = _errorMsg('Cannot run transaction: EIP 1559 is not activated.', vm, opts.block, opts.tx);
303
+ throw EthereumJSErrorWithoutCode(msg);
304
+ }
305
+ if (!isFrameEIP8141Tx(opts.tx)) {
306
+ const castedTx = opts.tx;
307
+ for (const accessListItem of castedTx.accessList) {
308
+ const [addressBytes, slotBytesList] = accessListItem;
309
+ // Using deprecated bytesToUnprefixedHex for performance: journal methods expect unprefixed hex strings for Map/Set lookups.
310
+ const address = bytesToUnprefixedHex(addressBytes);
311
+ vm.evm.journal.addAlwaysWarmAddress(address, true);
312
+ for (const storageKey of slotBytesList) {
313
+ vm.evm.journal.addAlwaysWarmSlot(address, bytesToUnprefixedHex(storageKey), true);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ try {
319
+ const result = await _runTx(vm, opts);
320
+ await vm.evm.journal.commit();
321
+ if (vm.DEBUG) {
322
+ debug(`tx checkpoint committed`);
323
+ }
324
+ return result;
325
+ }
326
+ catch (e) {
327
+ await vm.evm.journal.revert();
328
+ if (vm.DEBUG) {
329
+ debug(`tx checkpoint reverted`);
330
+ }
331
+ throw e;
332
+ }
333
+ finally {
334
+ if (vm.common.isActivatedEIP(2929)) {
335
+ vm.evm.journal.cleanJournal();
336
+ }
337
+ vm.evm.stateManager.originalStorageCache.clear();
338
+ if (enableProfiler) {
339
+ // eslint-disable-next-line no-console
340
+ console.timeEnd(entireTxLabel);
341
+ const logs = vm.evm.getPerformanceLogs();
342
+ if (logs.precompiles.length === 0 && logs.opcodes.length === 0) {
343
+ // eslint-disable-next-line no-console
344
+ console.log('No precompile or opcode execution.');
345
+ }
346
+ emitEVMProfile(logs.precompiles, 'Precompile performance');
347
+ emitEVMProfile(logs.opcodes, 'Opcodes performance');
348
+ vm.evm.clearPerformanceLogs();
349
+ }
350
+ }
351
+ }
352
+ async function _runTx(vm, opts) {
353
+ const state = vm.stateManager;
354
+ // ===========================
355
+ // SETUP: Binary Tree Witness
356
+ // ===========================
357
+ let stateAccesses;
358
+ let txAccesses;
359
+ if (vm.common.isActivatedEIP(7864)) {
360
+ if (vm.evm.binaryTreeAccessWitness === undefined) {
361
+ throw Error(`Binary tree access witness needed for execution of binary tree blocks`);
362
+ }
363
+ // Check if statemanager is a BinaryTreeStateManager by checking for a method only on BinaryTreeStateManager API
364
+ if (!('verifyBinaryPostState' in vm.stateManager)) {
365
+ throw EthereumJSErrorWithoutCode(`Binary tree State Manager needed for execution of binary tree blocks`);
366
+ }
367
+ stateAccesses = vm.evm.binaryTreeAccessWitness;
368
+ txAccesses = new BinaryTreeAccessWitness({
369
+ hashFunction: vm.evm.binaryTreeAccessWitness.hashFunction,
370
+ });
371
+ }
372
+ // ===========================
373
+ // SETUP: Transaction and Events
374
+ // ===========================
375
+ const { tx, block } = opts;
376
+ /** The `beforeTx` event - emits the Transaction that is about to be processed */
377
+ await vm._emit('beforeTx', tx);
378
+ const caller = tx.getSenderAddress();
379
+ if (vm.DEBUG) {
380
+ debug(`New tx run hash=${opts.tx.isSigned() ? bytesToHex(opts.tx.hash()) : 'unsigned'} sender=${caller}`);
381
+ }
382
+ // ===========================
383
+ // SETUP: Address Warming (EIP-2929)
384
+ // ===========================
385
+ if (vm.common.isActivatedEIP(2929)) {
386
+ // Add origin, precompiles, and relevant addresses to warm set
387
+ const activePrecompiles = vm.evm.precompiles;
388
+ for (const [addressStr] of activePrecompiles.entries()) {
389
+ vm.evm.journal.addAlwaysWarmAddress(addressStr);
390
+ }
391
+ vm.evm.journal.addAlwaysWarmAddress(caller.toString());
392
+ if (tx.to !== undefined) {
393
+ // Note: in case we create a contract, we do vm in EVMs `_executeCreate` (vm is also correct in inner calls, per the EIP)
394
+ // Using deprecated bytesToUnprefixedHex for performance: journal methods expect unprefixed hex strings.
395
+ vm.evm.journal.addAlwaysWarmAddress(bytesToUnprefixedHex(tx.to.bytes));
396
+ }
397
+ if (vm.common.isActivatedEIP(3651)) {
398
+ const coinbase = block?.header.coinbase.bytes ?? DEFAULT_HEADER.coinbase.bytes;
399
+ // Using deprecated bytesToUnprefixedHex for performance: journal methods expect unprefixed hex strings.
400
+ vm.evm.journal.addAlwaysWarmAddress(bytesToUnprefixedHex(coinbase));
401
+ }
402
+ }
403
+ // ===========================
404
+ // VALIDATION: Gas Limit and Fees
405
+ // ===========================
406
+ // Validate gas limit against tx base fee (DataFee + TxFee + Creation Fee)
407
+ const intrinsicGas = tx.getIntrinsicGas();
408
+ let floorCost = BIGINT_0;
409
+ // EIP-7623: Calculate floor cost for calldata
410
+ if (vm.common.isActivatedEIP(7623)) {
411
+ // Tx should at least cover the floor price for tx data
412
+ let tokens = 0;
413
+ for (let i = 0; i < tx.data.length; i++) {
414
+ tokens += tx.data[i] === 0 ? 1 : 4;
415
+ }
416
+ floorCost =
417
+ tx.common.param('txGas') + tx.common.param('totalCostFloorPerToken') * BigInt(tokens);
418
+ }
419
+ let gasLimit = tx.gasLimit;
420
+ const minGasLimit = bigIntMax(intrinsicGas, floorCost);
421
+ if (gasLimit < minGasLimit) {
422
+ const msg = _errorMsg(`tx gas limit ${Number(gasLimit)} is lower than the minimum gas limit of ${Number(minGasLimit)}`, vm, block, tx);
423
+ throw EthereumJSErrorWithoutCode(msg);
424
+ }
425
+ gasLimit -= intrinsicGas;
426
+ if (vm.DEBUG) {
427
+ debugGas(`Subtracting base fee (${intrinsicGas}) from gasLimit (-> ${gasLimit})`);
428
+ }
429
+ if (vm.common.isActivatedEIP(1559)) {
430
+ // EIP-1559 spec:
431
+ // Ensure that the user was willing to at least pay the base fee
432
+ // assert transaction.max_fee_per_gas >= block.base_fee_per_gas
433
+ const maxFeePerGas = 'maxFeePerGas' in tx ? tx.maxFeePerGas : tx.gasPrice;
434
+ const baseFeePerGas = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas;
435
+ if (maxFeePerGas < baseFeePerGas) {
436
+ const msg = _errorMsg(`Transaction's ${'maxFeePerGas' in tx ? 'maxFeePerGas' : 'gasPrice'} (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, vm, block, tx);
437
+ throw EthereumJSErrorWithoutCode(msg);
438
+ }
439
+ }
440
+ if (enableProfiler) {
441
+ // eslint-disable-next-line no-console
442
+ console.timeEnd(initLabel);
443
+ // eslint-disable-next-line no-console
444
+ console.time(balanceNonceLabel);
445
+ }
446
+ // ===========================
447
+ // VALIDATION: Sender Account
448
+ // ===========================
449
+ let fromAccount = await state.getAccount(caller);
450
+ if (fromAccount === undefined) {
451
+ fromAccount = new Account();
452
+ }
453
+ const { nonce, balance } = fromAccount;
454
+ if (vm.DEBUG) {
455
+ debug(`Sender's pre-tx balance is ${balance}`);
456
+ }
457
+ // EIP-3607: Reject transactions from senders with deployed code
458
+ // EIP-8141 frame transactions allow smart accounts (sender may have code)
459
+ if (!equalsBytes(fromAccount.codeHash, KECCAK256_NULL) && !isFrameEIP8141Tx(tx)) {
460
+ const isActive7702 = vm.common.isActivatedEIP(7702);
461
+ switch (isActive7702) {
462
+ case true: {
463
+ const code = await state.getCode(caller);
464
+ // If the EOA is 7702-delegated, sending txs from this EOA is fine
465
+ if (equalsBytes(code.slice(0, 3), DELEGATION_7702_FLAG))
466
+ break;
467
+ // Trying to send TX from account with code (which is not 7702-delegated), falls through and throws
468
+ }
469
+ default: {
470
+ const msg = _errorMsg('invalid sender address, address is not EOA (EIP-3607)', vm, block, tx);
471
+ throw EthereumJSErrorWithoutCode(msg);
472
+ }
473
+ }
474
+ }
475
+ // Check balance against upfront tx cost
476
+ const baseFeePerGas = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas;
477
+ const upFrontCost = tx.getUpfrontCost(baseFeePerGas);
478
+ if (balance < upFrontCost) {
479
+ if (opts.skipBalance === true && fromAccount.balance < upFrontCost) {
480
+ if (tx.supports(Capability.EIP1559FeeMarket) === false) {
481
+ // if skipBalance and not EIP1559 transaction, ensure caller balance is enough to run transaction
482
+ const originalBalance = fromAccount.balance;
483
+ fromAccount.balance = upFrontCost;
484
+ await vm.evm.journal.putAccount(caller, fromAccount);
485
+ if (vm.common.isActivatedEIP(7928)) {
486
+ vm.evm.blockLevelAccessList.addBalanceChange(caller.toString(), fromAccount.balance, vm.evm.blockLevelAccessList.blockAccessIndex, originalBalance);
487
+ }
488
+ }
489
+ }
490
+ else {
491
+ const msg = _errorMsg(`sender doesn't have enough funds to send tx. The upfront cost is: ${upFrontCost} and the sender's account (${caller}) only has: ${balance}`, vm, block, tx);
492
+ throw EthereumJSErrorWithoutCode(msg);
493
+ }
494
+ }
495
+ // Check balance against max potential cost (for EIP 1559 and 4844)
496
+ let maxCost = tx.value;
497
+ let blobGasPrice = BIGINT_0;
498
+ let totalblobGas = BIGINT_0;
499
+ if (tx.supports(Capability.EIP1559FeeMarket)) {
500
+ // EIP-1559 spec:
501
+ // The signer must be able to afford the transaction
502
+ // `assert balance >= gas_limit * max_fee_per_gas`
503
+ maxCost += tx.gasLimit * tx.maxFeePerGas;
504
+ }
505
+ if (isBlob4844Tx(tx)) {
506
+ if (!vm.common.isActivatedEIP(4844)) {
507
+ const msg = _errorMsg('blob transactions are only valid with EIP4844 active', vm, block, tx);
508
+ throw EthereumJSErrorWithoutCode(msg);
509
+ }
510
+ // EIP-4844 spec
511
+ // the signer must be able to afford the transaction
512
+ // assert signer(tx).balance >= tx.message.gas * tx.message.max_fee_per_gas + get_total_data_gas(tx) * tx.message.max_fee_per_data_gas
513
+ totalblobGas = vm.common.param('blobGasPerBlob') * BigInt(tx.numBlobs());
514
+ maxCost += totalblobGas * tx.maxFeePerBlobGas;
515
+ // 4844 minimum blobGas price check
516
+ blobGasPrice = opts.block?.header.getBlobGasPrice() ?? DEFAULT_HEADER.getBlobGasPrice();
517
+ if (tx.maxFeePerBlobGas < blobGasPrice) {
518
+ const msg = _errorMsg(`Transaction's maxFeePerBlobGas ${tx.maxFeePerBlobGas}) is less than block blobGasPrice (${blobGasPrice}).`, vm, block, tx);
519
+ throw EthereumJSErrorWithoutCode(msg);
520
+ }
521
+ }
522
+ if (fromAccount.balance < maxCost) {
523
+ if (opts.skipBalance === true && fromAccount.balance < maxCost) {
524
+ // if skipBalance, ensure caller balance is enough to run transaction
525
+ const originalBalance = fromAccount.balance;
526
+ fromAccount.balance = maxCost;
527
+ await vm.evm.journal.putAccount(caller, fromAccount);
528
+ if (vm.common.isActivatedEIP(7928)) {
529
+ vm.evm.blockLevelAccessList.addBalanceChange(caller.toString(), fromAccount.balance, vm.evm.blockLevelAccessList.blockAccessIndex, originalBalance);
530
+ }
531
+ }
532
+ else {
533
+ const msg = _errorMsg(`sender doesn't have enough funds to send tx. The max cost is: ${maxCost} and the sender's account (${caller}) only has: ${balance}`, vm, block, tx);
534
+ throw EthereumJSErrorWithoutCode(msg);
535
+ }
536
+ }
537
+ if (opts.skipNonce !== true) {
538
+ if (nonce !== tx.nonce) {
539
+ const msg = _errorMsg(`the tx doesn't have the correct nonce. account has nonce of: ${nonce} tx has nonce of: ${tx.nonce}`, vm, block, tx);
540
+ throw EthereumJSErrorWithoutCode(msg);
541
+ }
542
+ }
543
+ // ===========================
544
+ // CALCULATION: Gas Price
545
+ // ===========================
546
+ let gasPrice;
547
+ let inclusionFeePerGas;
548
+ if (tx.supports(Capability.EIP1559FeeMarket)) {
549
+ // TODO make txs use the new getEffectivePriorityFee
550
+ const baseFee = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas;
551
+ inclusionFeePerGas = tx.getEffectivePriorityFee(baseFee);
552
+ gasPrice = inclusionFeePerGas + baseFee;
553
+ }
554
+ else {
555
+ // Have to cast as legacy tx since EIP1559 tx does not have gas price
556
+ gasPrice = tx.gasPrice;
557
+ if (vm.common.isActivatedEIP(1559)) {
558
+ const baseFee = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas;
559
+ inclusionFeePerGas = tx.gasPrice - baseFee;
560
+ }
561
+ }
562
+ // EIP-4844 tx
563
+ let blobVersionedHashes;
564
+ if (isBlob4844Tx(tx)) {
565
+ blobVersionedHashes = tx.blobVersionedHashes;
566
+ }
567
+ // ===========================
568
+ // EIP-8141: Frame Transaction Execution (separate path)
569
+ // ===========================
570
+ let results;
571
+ let txCost;
572
+ let blobGasCost;
573
+ let gasRefund = BIGINT_0;
574
+ let executionTimerPrecise;
575
+ if (isFrameEIP8141Tx(tx)) {
576
+ // Frame transactions: gas deduction and nonce increment happen inside APPROVE
577
+ txCost = BIGINT_0;
578
+ blobGasCost = BIGINT_0;
579
+ if (enableProfiler) {
580
+ // eslint-disable-next-line no-console
581
+ console.timeEnd(balanceNonceLabel);
582
+ executionTimerPrecise = performance.now();
583
+ }
584
+ results = await runFrameTransaction(vm, tx, gasPrice, blobGasPrice, intrinsicGas, block, opts);
585
+ }
586
+ else {
587
+ // ===========================
588
+ // STATE UPDATE: Deduct Costs (standard transactions)
589
+ // ===========================
590
+ txCost = tx.gasLimit * gasPrice;
591
+ blobGasCost = totalblobGas * blobGasPrice;
592
+ const senderOriginalBalance = fromAccount.balance;
593
+ fromAccount.balance -= txCost;
594
+ fromAccount.balance -= blobGasCost;
595
+ if (opts.skipBalance === true && fromAccount.balance < BIGINT_0) {
596
+ fromAccount.balance = BIGINT_0;
597
+ }
598
+ await vm.evm.journal.putAccount(caller, fromAccount);
599
+ if (vm.common.isActivatedEIP(7928)) {
600
+ vm.evm.blockLevelAccessList.addBalanceChange(caller.toString(), fromAccount.balance, vm.evm.blockLevelAccessList.blockAccessIndex, senderOriginalBalance);
601
+ }
602
+ // Process EIP-7702 authorization list (if applicable)
603
+ if (tx.supports(Capability.EIP7702EOACode)) {
604
+ gasRefund = await processAuthorizationList(vm, tx, caller, gasRefund);
605
+ }
606
+ if (vm.DEBUG) {
607
+ debug(`Update fromAccount (caller) balance (-> ${fromAccount.balance}))`);
608
+ }
609
+ if (enableProfiler) {
610
+ // eslint-disable-next-line no-console
611
+ console.timeEnd(balanceNonceLabel);
612
+ executionTimerPrecise = performance.now();
613
+ }
614
+ // ===========================
615
+ // EXECUTION: Run EVM Call
616
+ // ===========================
617
+ const { value, data, to } = tx;
618
+ if (vm.DEBUG) {
619
+ debug(`Running tx=${tx.isSigned() ? bytesToHex(tx.hash()) : 'unsigned'} with caller=${caller} gasLimit=${gasLimit} to=${to?.toString() ?? 'none'} value=${value} data=${short(data)}`);
620
+ }
621
+ results = (await vm.evm.runCall({
622
+ block,
623
+ gasPrice,
624
+ caller,
625
+ gasLimit,
626
+ to,
627
+ value,
628
+ data,
629
+ blobVersionedHashes,
630
+ accessWitness: txAccesses,
631
+ }));
632
+ if (vm.common.isActivatedEIP(7864)) {
633
+ ;
634
+ stateAccesses?.merge(txAccesses);
635
+ }
636
+ }
637
+ if (enableProfiler) {
638
+ // eslint-disable-next-line no-console
639
+ console.log(`${executionLabel}: ${performance.now() - executionTimerPrecise}ms`);
640
+ // eslint-disable-next-line no-console
641
+ console.log('[ For execution details see table output ]');
642
+ // eslint-disable-next-line no-console
643
+ console.time(logsGasBalanceLabel);
644
+ }
645
+ if (vm.DEBUG) {
646
+ debug(`Update fromAccount (caller) nonce (-> ${fromAccount.nonce})`);
647
+ }
648
+ if (vm.DEBUG) {
649
+ const { executionGasUsed, exceptionError, returnValue } = results.execResult;
650
+ debug('-'.repeat(100));
651
+ debug(`Received tx execResult: [ executionGasUsed=${executionGasUsed} exceptionError=${exceptionError !== undefined ? `'${exceptionError.error}'` : 'none'} returnValue=${short(returnValue)} gasRefund=${results.gasRefund ?? 0} ]`);
652
+ }
653
+ // ===========================
654
+ // RESULTS: Gas and Balances
655
+ // ===========================
656
+ // Calculate tx gas used before refund processing
657
+ const totalGasSpentBeforeRefund = results.execResult.executionGasUsed + intrinsicGas;
658
+ results.totalGasSpent = totalGasSpentBeforeRefund;
659
+ if (vm.DEBUG) {
660
+ debugGas(`tx add baseFee ${intrinsicGas} to totalGasSpent (-> ${results.totalGasSpent})`);
661
+ }
662
+ // Add blob gas used to result
663
+ if (isBlob4844Tx(tx)) {
664
+ results.blobGasUsed = totalblobGas;
665
+ }
666
+ // Process any gas refund
667
+ gasRefund += results.execResult.gasRefund ?? BIGINT_0;
668
+ results.gasRefund = gasRefund; // TODO: this field could now be incorrect with the introduction of 7623
669
+ const maxRefundQuotient = vm.common.param('maxRefundQuotient');
670
+ if (gasRefund !== BIGINT_0) {
671
+ const maxRefund = results.totalGasSpent / maxRefundQuotient;
672
+ gasRefund = gasRefund < maxRefund ? gasRefund : maxRefund;
673
+ results.totalGasSpent -= gasRefund;
674
+ if (vm.DEBUG) {
675
+ debug(`Subtract tx gasRefund (${gasRefund}) from totalGasSpent (-> ${results.totalGasSpent})`);
676
+ }
677
+ }
678
+ else {
679
+ if (vm.DEBUG) {
680
+ debug(`No tx gasRefund`);
681
+ }
682
+ }
683
+ if (vm.common.isActivatedEIP(7623)) {
684
+ if (results.totalGasSpent < floorCost) {
685
+ if (vm.DEBUG) {
686
+ debugGas(`tx floorCost ${floorCost} is higher than to total execution gas spent (-> ${results.totalGasSpent}), setting floor as gas paid`);
687
+ }
688
+ results.gasRefund = BIGINT_0;
689
+ results.totalGasSpent = floorCost;
690
+ }
691
+ }
692
+ // EIP-7778: block-level gas accounting does not subtract tx refunds.
693
+ // For pre-7778 forks this equals the amount paid by the sender.
694
+ results.blockGasSpent = vm.common.isActivatedEIP(7778)
695
+ ? bigIntMax(totalGasSpentBeforeRefund, floorCost)
696
+ : results.totalGasSpent;
697
+ results.amountSpent = results.totalGasSpent * gasPrice;
698
+ // Update sender/payer balance with gas refund
699
+ if (isFrameEIP8141Tx(tx)) {
700
+ // EIP-8141: refund unused gas to the payer (set by APPROVE)
701
+ const frameTx = tx;
702
+ const payer = frameTx.getSenderAddress(); // For Example 1, payer == sender
703
+ let payerAccount = await state.getAccount(payer);
704
+ if (payerAccount === undefined)
705
+ payerAccount = new Account();
706
+ const gasCostCharged = frameTx.gasLimit * gasPrice;
707
+ const actualGasCost = results.totalGasSpent * gasPrice;
708
+ const payerRefund = gasCostCharged - actualGasCost;
709
+ if (payerRefund > BIGINT_0) {
710
+ payerAccount.balance += payerRefund;
711
+ await vm.evm.journal.putAccount(payer, payerAccount);
712
+ }
713
+ if (vm.DEBUG) {
714
+ debug(`EIP-8141: Refunded ${payerRefund} to payer ${payer} (charged=${gasCostCharged} actual=${actualGasCost})`);
715
+ }
716
+ }
717
+ else {
718
+ fromAccount = await state.getAccount(caller);
719
+ if (fromAccount === undefined) {
720
+ fromAccount = new Account();
721
+ }
722
+ const actualTxCost = results.totalGasSpent * gasPrice;
723
+ const txCostDiff = txCost - actualTxCost;
724
+ const originalBalance = fromAccount.balance;
725
+ fromAccount.balance += txCostDiff;
726
+ if (vm.common.isActivatedEIP(7928)) {
727
+ vm.evm.blockLevelAccessList.addBalanceChange(caller.toString(), fromAccount.balance, vm.evm.blockLevelAccessList.blockAccessIndex, originalBalance);
728
+ vm.evm.blockLevelAccessList.addNonceChange(caller.toString(), fromAccount.nonce, vm.evm.blockLevelAccessList.blockAccessIndex);
729
+ }
730
+ await vm.evm.journal.putAccount(caller, fromAccount);
731
+ if (vm.common.isActivatedEIP(7928) && txCostDiff > BIGINT_0) {
732
+ vm.evm.blockLevelAccessList.addBalanceChange(caller.toString(), fromAccount.balance, vm.evm.blockLevelAccessList.blockAccessIndex);
733
+ }
734
+ if (vm.DEBUG) {
735
+ debug(`Refunded txCostDiff (${txCostDiff}) to fromAccount (caller) balance (-> ${fromAccount.balance})`);
736
+ }
737
+ }
738
+ // Update miner's balance
739
+ await updateMinerBalance(vm, state, block, results, inclusionFeePerGas);
740
+ if (enableProfiler) {
741
+ // eslint-disable-next-line no-console
742
+ console.timeEnd(logsGasBalanceLabel);
743
+ // eslint-disable-next-line no-console
744
+ console.time(accountsCleanUpLabel);
745
+ }
746
+ // ===========================
747
+ // CLEANUP: Accounts and State
748
+ // ===========================
749
+ await processSelfdestructs(vm, results);
750
+ // Generate the bloom after selfdestruct finalization logs have been appended.
751
+ results.bloom = txLogsBloom(results.execResult.logs, vm.common);
752
+ if (vm.DEBUG) {
753
+ debug(`Generated tx bloom with logs=${results.execResult.logs?.length}`);
754
+ }
755
+ if (enableProfiler) {
756
+ // eslint-disable-next-line no-console
757
+ console.timeEnd(accountsCleanUpLabel);
758
+ // eslint-disable-next-line no-console
759
+ console.time(accessListLabel);
760
+ }
761
+ // Build access list result if requested
762
+ if (opts.reportAccessList === true && vm.common.isActivatedEIP(2930)) {
763
+ results.accessList = buildAccessListResult(vm);
764
+ }
765
+ if (enableProfiler) {
766
+ // eslint-disable-next-line no-console
767
+ console.timeEnd(accessListLabel);
768
+ // eslint-disable-next-line no-console
769
+ console.time(journalCacheCleanUpLabel);
770
+ }
771
+ // Collect preimages if requested
772
+ if (opts.reportPreimages === true && vm.evm.journal.preimages !== undefined) {
773
+ results.preimages = vm.evm.journal.preimages;
774
+ }
775
+ // Clear journal and caches
776
+ await vm.evm.journal.cleanup();
777
+ state.originalStorageCache.clear();
778
+ if (enableProfiler) {
779
+ // eslint-disable-next-line no-console
780
+ console.timeEnd(journalCacheCleanUpLabel);
781
+ // eslint-disable-next-line no-console
782
+ console.time(receiptsLabel);
783
+ }
784
+ // ===========================
785
+ // FINALIZE: Receipt and Events
786
+ // ===========================
787
+ const gasUsed = opts.blockGasUsed ?? block?.header.gasUsed ?? DEFAULT_HEADER.gasUsed;
788
+ const cumulativeGasUsed = gasUsed + results.totalGasSpent;
789
+ results.receipt = await generateTxReceipt(vm, tx, results, cumulativeGasUsed, totalblobGas, blobGasPrice);
790
+ if (enableProfiler) {
791
+ // eslint-disable-next-line no-console
792
+ console.timeEnd(receiptsLabel);
793
+ }
794
+ // EIP-7928: Clean up net-zero balance changes
795
+ // Per spec, if an account's balance changed during tx but final == pre-tx, don't record
796
+ if (vm.common.isActivatedEIP(7928)) {
797
+ vm.evm.blockLevelAccessList.cleanupNetZeroBalanceChanges();
798
+ }
799
+ /** The `afterTx` event - emits transaction results */
800
+ const event = { transaction: tx, ...results };
801
+ await vm._emit('afterTx', event);
802
+ if (vm.DEBUG) {
803
+ debug(`tx run finished hash=${opts.tx.isSigned() ? bytesToHex(opts.tx.hash()) : 'unsigned'} sender=${caller}`);
804
+ }
805
+ return results;
806
+ }
807
+ /**
808
+ * @method txLogsBloom
809
+ * @private
810
+ */
811
+ function txLogsBloom(logs, common) {
812
+ const bloom = new Bloom(undefined, common);
813
+ if (logs) {
814
+ for (let i = 0; i < logs.length; i++) {
815
+ const log = logs[i];
816
+ // add the address
817
+ bloom.add(log[0]);
818
+ // add the topics
819
+ const topics = log[1];
820
+ for (let q = 0; q < topics.length; q++) {
821
+ bloom.add(topics[q]);
822
+ }
823
+ }
824
+ }
825
+ return bloom;
826
+ }
827
+ /**
828
+ * Returns the tx receipt.
829
+ * @param vm The vm instance
830
+ * @param tx The transaction
831
+ * @param txResult The tx result
832
+ * @param cumulativeGasUsed The gas used in the block including vm tx
833
+ * @param blobGasUsed The blob gas used in the tx
834
+ * @param blobGasPrice The blob gas price for the block including vm tx
835
+ */
836
+ export async function generateTxReceipt(vm, tx, txResult, cumulativeGasUsed, blobGasUsed, blobGasPrice) {
837
+ const baseReceipt = {
838
+ cumulativeBlockGasUsed: cumulativeGasUsed,
839
+ bitvector: txResult.bloom.bitvector,
840
+ logs: txResult.execResult.logs ?? [],
841
+ };
842
+ let receipt;
843
+ if (vm.DEBUG) {
844
+ debug(`Generate tx receipt transactionType=${tx.type} cumulativeBlockGasUsed=${cumulativeGasUsed} bitvector=${short(baseReceipt.bitvector)} (${baseReceipt.bitvector.length} bytes) logs=${baseReceipt.logs.length}`);
845
+ }
846
+ if (!tx.supports(Capability.EIP2718TypedTransaction)) {
847
+ // Legacy transaction
848
+ if (vm.common.gteHardfork(Hardfork.Byzantium)) {
849
+ // Post-Byzantium
850
+ receipt = {
851
+ status: txResult.execResult.exceptionError !== undefined ? 0 : 1, // Receipts have a 0 as status on error
852
+ ...baseReceipt,
853
+ };
854
+ }
855
+ else {
856
+ // Pre-Byzantium
857
+ const stateRoot = await vm.stateManager.getStateRoot();
858
+ receipt = {
859
+ stateRoot,
860
+ ...baseReceipt,
861
+ };
862
+ }
863
+ }
864
+ else {
865
+ // Typed EIP-2718 Transaction
866
+ if (isBlob4844Tx(tx)) {
867
+ receipt = {
868
+ blobGasUsed,
869
+ blobGasPrice,
870
+ status: txResult.execResult.exceptionError ? 0 : 1,
871
+ ...baseReceipt,
872
+ };
873
+ }
874
+ else {
875
+ receipt = {
876
+ status: txResult.execResult.exceptionError ? 0 : 1,
877
+ ...baseReceipt,
878
+ };
879
+ }
880
+ }
881
+ return receipt;
882
+ }
883
+ /**
884
+ * Internal helper function to create an annotated error message
885
+ *
886
+ * @param msg Base error message
887
+ * @hidden
888
+ */
889
+ function _errorMsg(msg, vm, block, tx) {
890
+ const blockOrHeader = block ?? DEFAULT_HEADER;
891
+ const blockErrorStr = 'errorStr' in blockOrHeader ? blockOrHeader.errorStr() : 'block';
892
+ const txErrorStr = 'errorStr' in tx ? tx.errorStr() : 'tx';
893
+ const errorMsg = `${msg} (${vm.errorStr()} -> ${blockErrorStr} -> ${txErrorStr})`;
894
+ return errorMsg;
895
+ }
896
+ //# sourceMappingURL=runTx.js.map