UNPKG

@ethereumjs/vm

Version:
716 lines 31.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runTx = runTx; exports.generateTxReceipt = generateTxReceipt; const block_1 = require("@ethereumjs/block"); const common_1 = require("@ethereumjs/common"); const evm_1 = require("@ethereumjs/evm"); const tx_1 = require("@ethereumjs/tx"); const util_1 = require("@ethereumjs/util"); const debug_1 = require("debug"); const index_ts_1 = require("./bloom/index.js"); const emitEVMProfile_ts_1 = require("./emitEVMProfile.js"); const debug = (0, debug_1.default)('vm:tx'); const debugGas = (0, debug_1.default)('vm:tx:gas'); const DEFAULT_HEADER = (0, block_1.createBlockHeader)(); let enableProfiler = false; const initLabel = 'EVM journal init, address/slot warming, fee validation'; const balanceNonceLabel = 'Balance/Nonce checks and update'; const executionLabel = 'Execution'; const logsGasBalanceLabel = 'Logs, gas usage, account/miner balances'; const accountsCleanUpLabel = 'Accounts clean up'; const accessListLabel = 'Access list label'; const journalCacheCleanUpLabel = 'Journal/cache cleanup'; const receiptsLabel = 'Receipts'; const entireTxLabel = 'Entire tx'; // EIP-7702 flag: if contract code starts with these 3 bytes, it is a 7702-delegated EOA const DELEGATION_7702_FLAG = new Uint8Array([0xef, 0x01, 0x00]); /** * @ignore */ async function runTx(vm, opts) { if (vm['_opts'].profilerOpts?.reportAfterTx === true) { enableProfiler = true; } if (enableProfiler) { const title = `Profiler run - Tx ${(0, util_1.bytesToHex)(opts.tx.hash())}`; // eslint-disable-next-line no-console console.log(title); // eslint-disable-next-line no-console console.time(initLabel); // eslint-disable-next-line no-console console.time(entireTxLabel); } if (opts.skipHardForkValidation !== true && opts.block !== undefined) { // If block and tx don't have a same hardfork, set tx hardfork to block if (opts.tx.common.hardfork() !== opts.block.common.hardfork()) { opts.tx.common.setHardfork(opts.block.common.hardfork()); } if (opts.block.common.hardfork() !== vm.common.hardfork()) { // Block and VM's hardfork should match as well const msg = _errorMsg('block has a different hardfork than the vm', vm, opts.block, opts.tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } const gasLimit = opts.block?.header.gasLimit ?? DEFAULT_HEADER.gasLimit; if (opts.skipBlockGasLimitValidation !== true && gasLimit < opts.tx.gasLimit) { const msg = _errorMsg('tx has a higher gas limit than the block', vm, opts.block, opts.tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } // Ensure we start with a clear warmed accounts Map await vm.evm.journal.cleanup(); if (opts.reportAccessList === true) { vm.evm.journal.startReportingAccessList(); } if (opts.reportPreimages === true) { vm.evm.journal.startReportingPreimages(); } await vm.evm.journal.checkpoint(); if (vm.DEBUG) { debug('-'.repeat(100)); debug(`tx checkpoint`); } // Typed transaction specific setup tasks if (opts.tx.supports(tx_1.Capability.EIP2718TypedTransaction) && vm.common.isActivatedEIP(2718)) { // Is it an Access List transaction? if (!vm.common.isActivatedEIP(2930)) { await vm.evm.journal.revert(); const msg = _errorMsg('Cannot run transaction: EIP 2930 is not activated.', vm, opts.block, opts.tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } if (opts.tx.supports(tx_1.Capability.EIP1559FeeMarket) && !vm.common.isActivatedEIP(1559)) { await vm.evm.journal.revert(); const msg = _errorMsg('Cannot run transaction: EIP 1559 is not activated.', vm, opts.block, opts.tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } const castedTx = opts.tx; for (const accessListItem of castedTx.accessList) { const [addressBytes, slotBytesList] = accessListItem; const address = (0, util_1.bytesToUnprefixedHex)(addressBytes); // Note: in here, the 0x is stripped, so immediately do this here vm.evm.journal.addAlwaysWarmAddress(address, true); for (const storageKey of slotBytesList) { vm.evm.journal.addAlwaysWarmSlot(address, (0, util_1.bytesToUnprefixedHex)(storageKey), true); } } } try { const result = await _runTx(vm, opts); await vm.evm.journal.commit(); if (vm.DEBUG) { debug(`tx checkpoint committed`); } return result; } catch (e) { await vm.evm.journal.revert(); if (vm.DEBUG) { debug(`tx checkpoint reverted`); } throw e; } finally { if (vm.common.isActivatedEIP(2929)) { vm.evm.journal.cleanJournal(); } vm.evm.stateManager.originalStorageCache.clear(); if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(entireTxLabel); const logs = vm.evm.getPerformanceLogs(); if (logs.precompiles.length === 0 && logs.opcodes.length === 0) { // eslint-disable-next-line no-console console.log('No precompile or opcode execution.'); } (0, emitEVMProfile_ts_1.emitEVMProfile)(logs.precompiles, 'Precompile performance'); (0, emitEVMProfile_ts_1.emitEVMProfile)(logs.opcodes, 'Opcodes performance'); vm.evm.clearPerformanceLogs(); } } } async function _runTx(vm, opts) { const state = vm.stateManager; let stateAccesses; let txAccesses; if (vm.common.isActivatedEIP(7864)) { if (vm.evm.binaryTreeAccessWitness === undefined) { throw Error(`Binary tree access witness needed for execution of binary tree blocks`); } // Check if statemanager is a BinaryTreeStateManager by checking for a method only on BinaryTreeStateManager API if (!('verifyBinaryPostState' in vm.stateManager)) { throw (0, util_1.EthereumJSErrorWithoutCode)(`Binary tree State Manager needed for execution of binary tree blocks`); } stateAccesses = vm.evm.binaryTreeAccessWitness; txAccesses = new evm_1.BinaryTreeAccessWitness({ hashFunction: vm.evm.binaryTreeAccessWitness.hashFunction, }); } const { tx, block } = opts; /** * The `beforeTx` event * * @event Event: beforeTx * @type {Object} * @property {Transaction} tx emits the Transaction that is about to be processed */ await vm._emit('beforeTx', tx); const caller = tx.getSenderAddress(); if (vm.DEBUG) { debug(`New tx run hash=${opts.tx.isSigned() ? (0, util_1.bytesToHex)(opts.tx.hash()) : 'unsigned'} sender=${caller}`); } if (vm.common.isActivatedEIP(2929)) { // Add origin and precompiles to warm addresses const activePrecompiles = vm.evm.precompiles; for (const [addressStr] of activePrecompiles.entries()) { vm.evm.journal.addAlwaysWarmAddress(addressStr); } vm.evm.journal.addAlwaysWarmAddress(caller.toString()); if (tx.to !== undefined) { // Note: in case we create a contract, we do vm in EVMs `_executeCreate` (vm is also correct in inner calls, per the EIP) vm.evm.journal.addAlwaysWarmAddress((0, util_1.bytesToUnprefixedHex)(tx.to.bytes)); } if (vm.common.isActivatedEIP(3651)) { const coinbase = block?.header.coinbase.bytes ?? DEFAULT_HEADER.coinbase.bytes; vm.evm.journal.addAlwaysWarmAddress((0, util_1.bytesToUnprefixedHex)(coinbase)); } } // Validate gas limit against tx base fee (DataFee + TxFee + Creation Fee) const intrinsicGas = tx.getIntrinsicGas(); let floorCost = util_1.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; const minGasLimit = (0, util_1.bigIntMax)(intrinsicGas, floorCost); if (gasLimit < minGasLimit) { const msg = _errorMsg(`tx gas limit ${Number(gasLimit)} is lower than the minimum gas limit of ${Number(minGasLimit)}`, vm, block, tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } gasLimit -= intrinsicGas; if (vm.DEBUG) { debugGas(`Subtracting base fee (${intrinsicGas}) from gasLimit (-> ${gasLimit})`); } if (vm.common.isActivatedEIP(1559)) { // EIP-1559 spec: // Ensure that the user was willing to at least pay the base fee // assert transaction.max_fee_per_gas >= block.base_fee_per_gas const maxFeePerGas = 'maxFeePerGas' in tx ? tx.maxFeePerGas : tx.gasPrice; const baseFeePerGas = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas; if (maxFeePerGas < baseFeePerGas) { const msg = _errorMsg(`Transaction's ${'maxFeePerGas' in tx ? 'maxFeePerGas' : 'gasPrice'} (${maxFeePerGas}) is less than the block's baseFeePerGas (${baseFeePerGas})`, vm, block, tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(initLabel); // eslint-disable-next-line no-console console.time(balanceNonceLabel); } // Check from account's balance and nonce let fromAccount = await state.getAccount(caller); if (fromAccount === undefined) { fromAccount = new util_1.Account(); } const { nonce, balance } = fromAccount; if (vm.DEBUG) { debug(`Sender's pre-tx balance is ${balance}`); } // EIP-3607: Reject transactions from senders with deployed code if (vm.common.isActivatedEIP(3607) && !(0, util_1.equalsBytes)(fromAccount.codeHash, util_1.KECCAK256_NULL)) { const isActive7702 = vm.common.isActivatedEIP(7702); switch (isActive7702) { case true: { const code = await state.getCode(caller); // If the EOA is 7702-delegated, sending txs from this EOA is fine if ((0, util_1.equalsBytes)(code.slice(0, 3), DELEGATION_7702_FLAG)) break; // Trying to send TX from account with code (which is not 7702-delegated), falls through and throws } default: { const msg = _errorMsg('invalid sender address, address is not EOA (EIP-3607)', vm, block, tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } } // Check balance against upfront tx cost const baseFeePerGas = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas; const upFrontCost = tx.getUpfrontCost(baseFeePerGas); if (balance < upFrontCost) { if (opts.skipBalance === true && fromAccount.balance < upFrontCost) { if (tx.supports(tx_1.Capability.EIP1559FeeMarket) === false) { // if skipBalance and not EIP1559 transaction, ensure caller balance is enough to run transaction fromAccount.balance = upFrontCost; await vm.evm.journal.putAccount(caller, fromAccount); } } else { 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); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } // Check balance against max potential cost (for EIP 1559 and 4844) let maxCost = tx.value; let blobGasPrice = util_1.BIGINT_0; let totalblobGas = util_1.BIGINT_0; if (tx.supports(tx_1.Capability.EIP1559FeeMarket)) { // EIP-1559 spec: // The signer must be able to afford the transaction // `assert balance >= gas_limit * max_fee_per_gas` maxCost += tx.gasLimit * tx.maxFeePerGas; } if ((0, tx_1.isBlob4844Tx)(tx)) { if (!vm.common.isActivatedEIP(4844)) { const msg = _errorMsg('blob transactions are only valid with EIP4844 active', vm, block, tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } // EIP-4844 spec // the signer must be able to afford the transaction // 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 totalblobGas = vm.common.param('blobGasPerBlob') * BigInt(tx.numBlobs()); maxCost += totalblobGas * tx.maxFeePerBlobGas; // 4844 minimum blobGas price check blobGasPrice = opts.block?.header.getBlobGasPrice() ?? DEFAULT_HEADER.getBlobGasPrice(); if (tx.maxFeePerBlobGas < blobGasPrice) { const msg = _errorMsg(`Transaction's maxFeePerBlobGas ${tx.maxFeePerBlobGas}) is less than block blobGasPrice (${blobGasPrice}).`, vm, block, tx); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } if (fromAccount.balance < maxCost) { if (opts.skipBalance === true && fromAccount.balance < maxCost) { // if skipBalance, ensure caller balance is enough to run transaction fromAccount.balance = maxCost; await vm.evm.journal.putAccount(caller, fromAccount); } else { 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); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } if (opts.skipNonce !== true) { if (nonce !== tx.nonce) { 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); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } let gasPrice; let inclusionFeePerGas; // EIP-1559 tx if (tx.supports(tx_1.Capability.EIP1559FeeMarket)) { // TODO make txs use the new getEffectivePriorityFee const baseFee = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas; inclusionFeePerGas = tx.getEffectivePriorityFee(baseFee); gasPrice = inclusionFeePerGas + baseFee; } else { // Have to cast as legacy tx since EIP1559 tx does not have gas price gasPrice = tx.gasPrice; if (vm.common.isActivatedEIP(1559)) { const baseFee = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas; inclusionFeePerGas = tx.gasPrice - baseFee; } } // EIP-4844 tx let blobVersionedHashes; if ((0, tx_1.isBlob4844Tx)(tx)) { blobVersionedHashes = tx.blobVersionedHashes; } // Update from account's balance const txCost = tx.gasLimit * gasPrice; const blobGasCost = totalblobGas * blobGasPrice; fromAccount.balance -= txCost; fromAccount.balance -= blobGasCost; if (opts.skipBalance === true && fromAccount.balance < util_1.BIGINT_0) { fromAccount.balance = util_1.BIGINT_0; } await vm.evm.journal.putAccount(caller, fromAccount); let gasRefund = util_1.BIGINT_0; if (tx.supports(tx_1.Capability.EIP7702EOACode)) { // Add contract code for authority tuples provided by EIP 7702 tx const authorizationList = tx.authorizationList; for (let i = 0; i < authorizationList.length; i++) { // Authority tuple validation const data = authorizationList[i]; const chainId = data[0]; const chainIdBN = (0, util_1.bytesToBigInt)(chainId); if (chainIdBN !== util_1.BIGINT_0 && chainIdBN !== vm.common.chainId()) { // Chain id does not match, continue continue; } // Address to take code from const address = data[1]; const nonce = data[2]; if ((0, util_1.bytesToBigInt)(nonce) >= util_1.MAX_UINT64) { // authority nonce >= 2^64 - 1. Bumping this nonce by one will not make this fit in an uint64. // EIPs PR: https://github.com/ethereum/EIPs/pull/8938 continue; } const s = data[5]; if ((0, util_1.bytesToBigInt)(s) > util_1.SECP256K1_ORDER_DIV_2) { // Malleability protection to avoid "flipping" a valid signature to get // another valid signature (which yields the same account on `ecrecover`) // This is invalid, so skip this auth tuple continue; } const yParity = (0, util_1.bytesToBigInt)(data[3]); if (yParity > util_1.BIGINT_1) { continue; } // Address to set code to let authority; try { authority = (0, util_1.eoaCode7702RecoverAuthority)(data); } catch { // Invalid signature, continue continue; } const accountMaybeUndefined = await vm.stateManager.getAccount(authority); const accountExists = accountMaybeUndefined !== undefined; const account = accountMaybeUndefined ?? new util_1.Account(); // Add authority address to warm addresses vm.evm.journal.addAlwaysWarmAddress(authority.toString()); if (account.isContract()) { const code = await vm.stateManager.getCode(authority); if (!(0, util_1.equalsBytes)(code.slice(0, 3), DELEGATION_7702_FLAG)) { // Account is a "normal" contract continue; } } // Nonce check if (caller.toString() === authority.toString()) { if (account.nonce + util_1.BIGINT_1 !== (0, util_1.bytesToBigInt)(nonce)) { // Edge case: caller is the authority, so is self-signing the delegation // In this case, we "virtually" bump the account nonce by one // We CANNOT put this updated nonce into the account trie, because then // the EVM will bump the nonce once again, thus resulting in a wrong nonce continue; } } else if (account.nonce !== (0, util_1.bytesToBigInt)(nonce)) { continue; } if (accountExists) { const refund = tx.common.param('perEmptyAccountCost') - tx.common.param('perAuthBaseGas'); gasRefund += refund; } account.nonce++; await vm.evm.journal.putAccount(authority, account); if ((0, util_1.equalsBytes)(address, new Uint8Array(20))) { // Special case (see EIP PR: https://github.com/ethereum/EIPs/pull/8929) // If delegated to the zero address, clear the delegation of authority await vm.stateManager.putCode(authority, new Uint8Array()); } else { const addressCode = (0, util_1.concatBytes)(DELEGATION_7702_FLAG, address); await vm.stateManager.putCode(authority, addressCode); } } } if (vm.DEBUG) { debug(`Update fromAccount (caller) balance (-> ${fromAccount.balance}))`); } let executionTimerPrecise; if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(balanceNonceLabel); executionTimerPrecise = performance.now(); } /* * Execute message */ const { value, data, to } = tx; if (vm.DEBUG) { debug(`Running tx=${tx.isSigned() ? (0, util_1.bytesToHex)(tx.hash()) : 'unsigned'} with caller=${caller} gasLimit=${gasLimit} to=${to?.toString() ?? 'none'} value=${value} data=${(0, util_1.short)(data)}`); } const results = (await vm.evm.runCall({ block, gasPrice, caller, gasLimit, to, value, data, blobVersionedHashes, accessWitness: txAccesses, })); if (vm.common.isActivatedEIP(7864)) { ; stateAccesses?.merge(txAccesses); } if (enableProfiler) { // eslint-disable-next-line no-console console.log(`${executionLabel}: ${performance.now() - executionTimerPrecise}ms`); // eslint-disable-next-line no-console console.log('[ For execution details see table output ]'); // eslint-disable-next-line no-console console.time(logsGasBalanceLabel); } if (vm.DEBUG) { debug(`Update fromAccount (caller) nonce (-> ${fromAccount.nonce})`); } if (vm.DEBUG) { const { executionGasUsed, exceptionError, returnValue } = results.execResult; debug('-'.repeat(100)); debug(`Received tx execResult: [ executionGasUsed=${executionGasUsed} exceptionError=${exceptionError !== undefined ? `'${exceptionError.error}'` : 'none'} returnValue=${(0, util_1.short)(returnValue)} gasRefund=${results.gasRefund ?? 0} ]`); } /* * Parse results */ // Generate the bloom for the tx results.bloom = txLogsBloom(results.execResult.logs, vm.common); if (vm.DEBUG) { debug(`Generated tx bloom with logs=${results.execResult.logs?.length}`); } // Calculate the total gas used results.totalGasSpent = results.execResult.executionGasUsed + intrinsicGas; if (vm.DEBUG) { debugGas(`tx add baseFee ${intrinsicGas} to totalGasSpent (-> ${results.totalGasSpent})`); } // Add blob gas used to result if ((0, tx_1.isBlob4844Tx)(tx)) { results.blobGasUsed = totalblobGas; } // Process any gas refund gasRefund += results.execResult.gasRefund ?? util_1.BIGINT_0; results.gasRefund = gasRefund; // TODO: this field could now be incorrect with the introduction of 7623 const maxRefundQuotient = vm.common.param('maxRefundQuotient'); if (gasRefund !== util_1.BIGINT_0) { const maxRefund = results.totalGasSpent / maxRefundQuotient; gasRefund = gasRefund < maxRefund ? gasRefund : maxRefund; results.totalGasSpent -= gasRefund; if (vm.DEBUG) { debug(`Subtract tx gasRefund (${gasRefund}) from totalGasSpent (-> ${results.totalGasSpent})`); } } else { if (vm.DEBUG) { debug(`No tx gasRefund`); } } if (vm.common.isActivatedEIP(7623)) { if (results.totalGasSpent < floorCost) { if (vm.DEBUG) { debugGas(`tx floorCost ${floorCost} is higher than to total execution gas spent (-> ${results.totalGasSpent}), setting floor as gas paid`); } results.gasRefund = util_1.BIGINT_0; results.totalGasSpent = floorCost; } } results.amountSpent = results.totalGasSpent * gasPrice; // Update sender's balance fromAccount = await state.getAccount(caller); if (fromAccount === undefined) { fromAccount = new util_1.Account(); } const actualTxCost = results.totalGasSpent * gasPrice; const txCostDiff = txCost - actualTxCost; fromAccount.balance += txCostDiff; await vm.evm.journal.putAccount(caller, fromAccount); if (vm.DEBUG) { debug(`Refunded txCostDiff (${txCostDiff}) to fromAccount (caller) balance (-> ${fromAccount.balance})`); } // Update miner's balance let miner; if (vm.common.consensusType() === common_1.ConsensusType.ProofOfAuthority) { miner = (0, block_1.cliqueSigner)(block?.header ?? DEFAULT_HEADER); } else { miner = block?.header.coinbase ?? DEFAULT_HEADER.coinbase; } let minerAccount = await state.getAccount(miner); if (minerAccount === undefined) { minerAccount = new util_1.Account(); } // add the amount spent on gas to the miner's account results.minerValue = vm.common.isActivatedEIP(1559) ? results.totalGasSpent * inclusionFeePerGas : results.amountSpent; minerAccount.balance += results.minerValue; // Put the miner account into the state. If the balance of the miner account remains zero, note that // the state.putAccount function puts vm into the "touched" accounts. This will thus be removed when // we clean the touched accounts below in case we are in a fork >= SpuriousDragon await vm.evm.journal.putAccount(miner, minerAccount); if (vm.DEBUG) { debug(`tx update miner account (${miner}) balance (-> ${minerAccount.balance})`); } if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(logsGasBalanceLabel); // eslint-disable-next-line no-console console.time(accountsCleanUpLabel); } /* * Cleanup accounts */ if (results.execResult.selfdestruct !== undefined) { for (const addressToSelfdestructHex of results.execResult.selfdestruct) { const address = new util_1.Address((0, util_1.hexToBytes)(addressToSelfdestructHex)); if (vm.common.isActivatedEIP(6780)) { // skip cleanup of addresses not in createdAddresses if (!results.execResult.createdAddresses.has(address.toString())) { continue; } } await vm.evm.journal.deleteAccount(address); if (vm.DEBUG) { debug(`tx selfdestruct on address=${address}`); } } } if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(accountsCleanUpLabel); // eslint-disable-next-line no-console console.time(accessListLabel); } if (opts.reportAccessList === true && vm.common.isActivatedEIP(2930)) { // Convert the Map to the desired type const accessList = []; for (const [address, set] of vm.evm.journal.accessList) { const item = { address: `0x${address}`, storageKeys: [], }; for (const slot of set) { item.storageKeys.push(`0x${slot}`); } accessList.push(item); } results.accessList = accessList; } if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(accessListLabel); // eslint-disable-next-line no-console console.time(journalCacheCleanUpLabel); } if (opts.reportPreimages === true && vm.evm.journal.preimages !== undefined) { results.preimages = vm.evm.journal.preimages; } await vm.evm.journal.cleanup(); state.originalStorageCache.clear(); if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(journalCacheCleanUpLabel); // eslint-disable-next-line no-console console.time(receiptsLabel); } // Generate the tx receipt const gasUsed = opts.blockGasUsed ?? block?.header.gasUsed ?? DEFAULT_HEADER.gasUsed; const cumulativeGasUsed = gasUsed + results.totalGasSpent; results.receipt = await generateTxReceipt(vm, tx, results, cumulativeGasUsed, totalblobGas, blobGasPrice); if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(receiptsLabel); } /** * The `afterTx` event * * @event Event: afterTx * @type {Object} * @property {Object} result result of the transaction */ const event = { transaction: tx, ...results }; await vm._emit('afterTx', event); if (vm.DEBUG) { debug(`tx run finished hash=${opts.tx.isSigned() ? (0, util_1.bytesToHex)(opts.tx.hash()) : 'unsigned'} sender=${caller}`); } return results; } /** * @method txLogsBloom * @private */ function txLogsBloom(logs, common) { const bloom = new index_ts_1.Bloom(undefined, common); if (logs) { for (let i = 0; i < logs.length; i++) { const log = logs[i]; // add the address bloom.add(log[0]); // add the topics const topics = log[1]; for (let q = 0; q < topics.length; q++) { bloom.add(topics[q]); } } } return bloom; } /** * Returns the tx receipt. * @param vm The vm instance * @param tx The transaction * @param txResult The tx result * @param cumulativeGasUsed The gas used in the block including vm tx * @param blobGasUsed The blob gas used in the tx * @param blobGasPrice The blob gas price for the block including vm tx */ async function generateTxReceipt(vm, tx, txResult, cumulativeGasUsed, blobGasUsed, blobGasPrice) { const baseReceipt = { cumulativeBlockGasUsed: cumulativeGasUsed, bitvector: txResult.bloom.bitvector, logs: txResult.execResult.logs ?? [], }; let receipt; if (vm.DEBUG) { debug(`Generate tx receipt transactionType=${tx.type} cumulativeBlockGasUsed=${cumulativeGasUsed} bitvector=${(0, util_1.short)(baseReceipt.bitvector)} (${baseReceipt.bitvector.length} bytes) logs=${baseReceipt.logs.length}`); } if (!tx.supports(tx_1.Capability.EIP2718TypedTransaction)) { // Legacy transaction if (vm.common.gteHardfork(common_1.Hardfork.Byzantium)) { // Post-Byzantium receipt = { status: txResult.execResult.exceptionError !== undefined ? 0 : 1, // Receipts have a 0 as status on error ...baseReceipt, }; } else { // Pre-Byzantium const stateRoot = await vm.stateManager.getStateRoot(); receipt = { stateRoot, ...baseReceipt, }; } } else { // Typed EIP-2718 Transaction if ((0, tx_1.isBlob4844Tx)(tx)) { receipt = { blobGasUsed, blobGasPrice, status: txResult.execResult.exceptionError ? 0 : 1, ...baseReceipt, }; } else { receipt = { status: txResult.execResult.exceptionError ? 0 : 1, ...baseReceipt, }; } } return receipt; } /** * Internal helper function to create an annotated error message * * @param msg Base error message * @hidden */ function _errorMsg(msg, vm, block, tx) { const blockOrHeader = block ?? DEFAULT_HEADER; const blockErrorStr = 'errorStr' in blockOrHeader ? blockOrHeader.errorStr() : 'block'; const txErrorStr = 'errorStr' in tx ? tx.errorStr() : 'tx'; const errorMsg = `${msg} (${vm.errorStr()} -> ${blockErrorStr} -> ${txErrorStr})`; return errorMsg; } //# sourceMappingURL=runTx.js.map