UNPKG

@ethereumjs/evm

Version:
253 lines 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.canAfford = canAfford; exports.newAccountStateGasCost = newAccountStateGasCost; exports.eip7928TrapPreTarget = eip7928TrapPreTarget; exports.eip7928SchedulePostTargetCallOog = eip7928SchedulePostTargetCallOog; exports.eip7928PostTargetCreateOog = eip7928PostTargetCreateOog; exports.consumeEip7928PostTargetCallOog = consumeEip7928PostTargetCallOog; exports.getCallNewAccountPostCosts = getCallNewAccountPostCosts; exports.callFamilyGas = callFamilyGas; exports.create7928Gas = create7928Gas; const common_1 = require("@ethereumjs/common"); const util_1 = require("@ethereumjs/util"); const eip8037_ts_1 = require("../eip8037.js"); const errors_ts_1 = require("../errors.js"); const EIP2929_ts_1 = require("./EIP2929.js"); const util_ts_1 = require("./util.js"); /** True when `regularGas + stateGas` fits in `gas_left + state_gas_reservoir` (EIP-8037). */ function canAfford(runState, regularGas, stateGas = util_1.BIGINT_0) { const gasLeft = runState.interpreter.getGasLeft(); const reservoir = runState.interpreter._evm.stateGasReservoir; return regularGas + stateGas <= gasLeft + reservoir; } function newAccountStateGasCost(common, runState) { if (!common.isActivatedEIP(8037)) { return util_1.BIGINT_0; } const stateBytesPerNewAccount = common.param('stateBytesPerNewAccount'); const blockGasLimit = runState.env.block.header.gasLimit; const costPerStateByte = (0, eip8037_ts_1.activeCostPerStateByte)(common, blockGasLimit); return stateBytesPerNewAccount * costPerStateByte; } /** Pre-target OOG: trap before any target access / BAL entry. */ function eip7928TrapPreTarget(runState, common, gas) { if (common.isActivatedEIP(7928) && gas > runState.interpreter.getGasLeft()) { (0, util_ts_1.trap)(errors_ts_1.EVMError.errorMessages.OUT_OF_GAS); } } /** Post-target CALL OOG: burn remaining frame gas and exceptional-halt in runStep. */ function eip7928SchedulePostTargetCallOog(runState, outOffset, outLength) { runState.eip7928PostTargetCallOog = { outOffset, outLength }; runState.interpreter._evm.eip7928CallPostTargetOog = true; return runState.interpreter.getGasLeft(); } /** Post-target CREATE OOG: charge accrued gas only; opcode pushes 0. */ function eip7928PostTargetCreateOog(runState, common, gas) { if (!common.isActivatedEIP(7928)) { (0, util_ts_1.trap)(errors_ts_1.EVMError.errorMessages.OUT_OF_GAS); } runState.messageGasLimit = util_1.BIGINT_0; runState.eip7928PostTargetCreateOog = true; return gas; } /** Post-target CALL OOG when 7928 is off (legacy: trap in gas handler). */ function postTargetCallOogOrTrap(runState, common, outOffset, outLength) { if (!common.isActivatedEIP(7928)) { (0, util_ts_1.trap)(errors_ts_1.EVMError.errorMessages.OUT_OF_GAS); } return eip7928SchedulePostTargetCallOog(runState, outOffset, outLength); } function consumeEip7928PostTargetCallOog(runState) { const pending = runState.eip7928PostTargetCallOog; if (pending === undefined) { return; } runState.eip7928PostTargetCallOog = undefined; (0, util_ts_1.writeCallOutput)(runState, pending.outOffset, pending.outLength); (0, util_ts_1.trap)(errors_ts_1.EVMError.errorMessages.OUT_OF_GAS); } async function getCallNewAccountPostCosts(runState, common, toAddress) { let createsNewAccount = false; if (common.gteHardfork(common_1.Hardfork.SpuriousDragon)) { const account = await runState.stateManager.getAccount(toAddress); if (account === undefined || account.isEmpty()) { createsNewAccount = true; } } else if ((await runState.stateManager.getAccount(toAddress)) === undefined) { createsNewAccount = true; } if (!createsNewAccount) { return { regular: util_1.BIGINT_0, state: util_1.BIGINT_0, createsNewAccount: false }; } return { regular: common.param('callNewAccountGas'), state: newAccountStateGasCost(common, runState), createsNewAccount: true, }; } function commitTargetAccess(runState, toAddress, common) { if (common.isActivatedEIP(2929)) { (0, EIP2929_ts_1.warmAddress)(runState, toAddress.bytes); } (0, EIP2929_ts_1.addAddressToBAL)(runState, toAddress.bytes, common); } function finalizeCallMessageGas(runState, common, gas, currentGasLimit, newAccountStateGas, outOffset, outLength) { const gasLimit = (0, util_ts_1.maxCallGas)(currentGasLimit, runState.interpreter.getGasLeft() - gas, runState, common); if (common.isActivatedEIP(7928)) { if (!canAfford(runState, gas, newAccountStateGas)) { return eip7928SchedulePostTargetCallOog(runState, outOffset, outLength); } } if (gasLimit > runState.interpreter.getGasLeft() - gas) { return postTargetCallOogOrTrap(runState, common, outOffset, outLength); } if (gas > runState.interpreter.getGasLeft()) { return postTargetCallOogOrTrap(runState, common, outOffset, outLength); } // EIP-8037: pre-charge the new-account state-gas BEFORE the inner call // runs. This matches the EELS amsterdam (tests-bal) reference, where // `charge_state_gas(STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE)` // happens in the CALL opcode body before the sub-frame is processed and // before the insufficient-balance check. The charge is unconditional // (per spec it sticks even when the sub-call later fails for insufficient // balance or reverts), so we charge here rather than after frame exit. // The post-frame charge in evm.ts is skipped under EIP-8037 to avoid // double-counting (see `_executeCall` post-execution block). if (common.isActivatedEIP(8037) && newAccountStateGas > util_1.BIGINT_0) { runState.interpreter.chargeStateGas(newAccountStateGas, 'CALL pre-charge new_account'); } runState.messageGasLimit = gasLimit; return gas; } /** * Shared EIP-2929 / EIP-7928 / EIP-8037 gas path for CALL, CALLCODE, DELEGATECALL, STATICCALL. */ async function callFamilyGas(runState, gas, common, opts) { const { currentGasLimit, toAddress, value, inOffset, inLength, outOffset, outLength, includeValueTransfer, includeNewAccountPostCheck, eip7702GetAccessCost, eip7702WarmAddress, } = opts; gas += (0, util_ts_1.subMemUsage)(runState, inOffset, inLength, common); gas += (0, util_ts_1.subMemUsage)(runState, outOffset, outLength, common); eip7928TrapPreTarget(runState, common, gas); let charge2929Gas = true; if ((common.isActivatedEIP(6800) || common.isActivatedEIP(7864)) && runState.interpreter._evm.getPrecompile(toAddress) === undefined) { const coldAccessGas = runState.env.accessWitness.readAccountBasicData(toAddress); if (includeValueTransfer && value !== util_1.BIGINT_0) { const contractAddress = runState.interpreter.getAddress(); gas += runState.env.accessWitness.writeAccountBasicData(contractAddress); gas += runState.env.accessWitness.writeAccountBasicData(toAddress); } gas += coldAccessGas; charge2929Gas = coldAccessGas === util_1.BIGINT_0; } if (common.isActivatedEIP(2929)) { gas += (0, EIP2929_ts_1.getAddressAccessCost)(runState, toAddress.bytes, common, charge2929Gas); } let valueTransferGas = util_1.BIGINT_0; if (includeValueTransfer && value !== util_1.BIGINT_0 && !common.isActivatedEIP(6800) && !common.isActivatedEIP(7864)) { valueTransferGas = common.param('callValueTransferGas'); } eip7928TrapPreTarget(runState, common, gas + valueTransferGas); commitTargetAccess(runState, toAddress, common); gas += valueTransferGas; let newAccountStateGas = util_1.BIGINT_0; // SpuriousDragon+ charges the new-account fee only on value transfers, but // pre-SpuriousDragon charges it for any call to a non-existent account. if (includeNewAccountPostCheck && (value !== util_1.BIGINT_0 || !common.gteHardfork(common_1.Hardfork.SpuriousDragon))) { const { regular, state } = await getCallNewAccountPostCosts(runState, common, toAddress); gas += regular; newAccountStateGas = state; } if (common.isActivatedEIP(7702)) { const { gas: delegationGas, delegationAddress } = await eip7702GetAccessCost(runState, common, toAddress, charge2929Gas); gas += delegationGas; if (delegationAddress !== null) { if (common.isActivatedEIP(7928)) { if (gas > runState.interpreter.getGasLeft()) { return postTargetCallOogOrTrap(runState, common, outOffset, outLength); } eip7702WarmAddress(runState, delegationAddress); (0, EIP2929_ts_1.addAddressToBAL)(runState, delegationAddress, common); } else { eip7702WarmAddress(runState, delegationAddress); } } } return finalizeCallMessageGas(runState, common, gas, currentGasLimit, newAccountStateGas, outOffset, outLength); } /** * Shared EIP-7928 / EIP-8037 gas path for CREATE and CREATE2. */ async function create7928Gas(runState, gas, common, opts, getCreateTargetAddressBytes) { const { offset, length, salt, extraPreTargetGas = util_1.BIGINT_0, preChargeLabel } = opts; // EIP-3860: reject oversized initcode BEFORE any state-gas pre-charge so // that EIP-8037's reservoir is preserved when the size check fails. This // matches the EELS amsterdam (tests-bal) reference, which raises // OutOfGasError from `generic_create` ahead of `charge_state_gas`. Without // this, the new-account state-gas pre-charge runs first and is hard to // unwind cleanly at the tx-root frame (depth=0): the frame-revert handler // refunds the spilled slice to a parent reservoir that doesn't exist, and // the user is overcharged by `STATE_BYTES_PER_NEW_ACCOUNT * costPerStateByte`. if (common.isActivatedEIP(3860) && length > Number(common.param('maxInitCodeSize')) && !runState.interpreter._evm.allowUnlimitedInitCodeSize) { (0, util_ts_1.trap)(errors_ts_1.EVMError.errorMessages.INITCODE_SIZE_VIOLATION); } gas += (0, util_ts_1.subMemUsage)(runState, offset, length, common); if (common.isActivatedEIP(3860)) { gas += ((length + util_1.BIGINT_31) / util_1.BIGINT_32) * common.param('initCodeWordGas'); } gas += extraPreTargetGas; eip7928TrapPreTarget(runState, common, gas); let initCode = new Uint8Array(0); if (length !== util_1.BIGINT_0) { initCode = runState.memory.read(Number(offset), Number(length), true); } const targetAddress = await getCreateTargetAddressBytes(runState, initCode, salt); const newAccountStateGas = newAccountStateGasCost(common, runState); if (common.isActivatedEIP(8037)) { if (!canAfford(runState, gas, newAccountStateGas)) { (0, util_ts_1.trap)(errors_ts_1.EVMError.errorMessages.OUT_OF_GAS); } } if (common.isActivatedEIP(2929)) { (0, EIP2929_ts_1.warmAddress)(runState, targetAddress); } if (common.isActivatedEIP(2929)) { gas += (0, EIP2929_ts_1.accessAddressEIP2929)(runState, runState.interpreter.getAddress().bytes, common, false); } if (common.isActivatedEIP(8037)) { if (!canAfford(runState, gas, newAccountStateGas)) { (0, EIP2929_ts_1.addAddressToBAL)(runState, targetAddress, common); return eip7928PostTargetCreateOog(runState, common, gas); } if (gas > runState.interpreter.getGasLeft()) { (0, EIP2929_ts_1.addAddressToBAL)(runState, targetAddress, common); return eip7928PostTargetCreateOog(runState, common, gas); } if (gas > util_1.BIGINT_0) { runState.interpreter.useGas(gas, preChargeLabel); gas = util_1.BIGINT_0; } runState.interpreter.chargeStateGas(newAccountStateGas, `${preChargeLabel} new_account`); } else if (gas > runState.interpreter.getGasLeft()) { (0, EIP2929_ts_1.addAddressToBAL)(runState, targetAddress, common); return eip7928PostTargetCreateOog(runState, common, gas); } let gasLimit = BigInt(runState.interpreter.getGasLeft()) - gas; gasLimit = (0, util_ts_1.maxCallGas)(gasLimit, gasLimit, runState, common); if (gas > runState.interpreter.getGasLeft()) { (0, EIP2929_ts_1.addAddressToBAL)(runState, targetAddress, common); return eip7928PostTargetCreateOog(runState, common, gas); } runState.messageGasLimit = gasLimit; return gas; } //# sourceMappingURL=EIP7928.js.map