@tevm/actions
Version:
A typesafe library for writing forge scripts in typescript
1,429 lines (1,411 loc) • 228 kB
JavaScript
import { ERC20 } from '@tevm/contract';
import { parseAbi, bytesToHex, hexToBytes, keccak256, createAccount, getAddress, toHex, encodeFunctionData, isHex, decodeFunctionResult, hexToBigInt, numberToHex, fromRlp, hexToNumber, encodeDeployData, decodeErrorResult, parseGwei, invariant, EthjsAccount, serializeTransaction, toBytes } from '@tevm/utils';
import { bytesToHex as bytesToHex$1, numberToBytes, RawContractError, getContractError, encodeFunctionData as encodeFunctionData$1, hexToBigInt as hexToBigInt$1, InternalRpcError, stringToHex, decodeFunctionResult as decodeFunctionResult$1, toFunctionSelector, decodeAbiParameters, hexToString, hexToBytes as hexToBytes$1 } from 'viem';
import { createAddress, createContractAddress } from '@tevm/address';
import { InternalError, ForkError, InvalidRequestError, InvalidAddressError, InvalidNonceError, InvalidBalanceError, UnreachableCodeError, MisconfiguredClientError, AccountNotFoundError, InvalidDeployedBytecodeError, InvalidStorageRootError, InvalidBlockError, UnknownBlockError, InvalidParamsError, InvalidGasPriceError, InvalidGasLimitError, GasLimitExceededError, InsufficientBalanceError, InternalEvmError, AuthCallUnsetError, CodeSizeExceedsMaximumError, CreateCollisionError, InvalidCommitmentError, EvmRevertError, InitcodeSizeViolationError, InvalidBeginSubError, InvalidBytecodeResultError, InvalidEofFormatError, InvalidInputLengthError, InvalidJumpError, InvalidJumpSubError, InvalidKzgInputsError, InvalidOpcodeError, InvalidProofError, InvalidReturnSubError, OutOfGasError, OutOfRangeError, RefundExhaustedError, StackOverflowError, StackUnderflowError, StaticStateChangeError, StopError, ValueOverflowError, BLS12381InputEmptyError, BLS12381FpNotInFieldError, BLS12381InvalidInputLengthError, BLS12381PointNotOnCurveError, CodeStoreOutOfGasError, InvalidSkipBalanceError, InvalidGasRefundError, InvalidOriginError, InvalidCallerError, InvalidValueError, InvalidDepthError, InvalidSelfdestructError, InvalidToError, InvalidBlobVersionedHashesError, InvalidMaxFeePerGasError, InvalidMaxPriorityFeePerGasError, InvalidAddToMempoolError, InvalidAddToBlockchainError, InvalidSaltError, InvalidDataError, InvalidBytecodeError, InvalidAbiError, InvalidArgsError, InvalidFunctionNameError, RevertError, DecodeFunctionDataError, MethodNotSupportedError, NoForkUrlSetError, MethodNotFoundError, InvalidTransactionError, BaseError, DefensiveNullCheckError, BlobGasLimitExceededError } from '@tevm/errors';
import { prefundedAccounts } from '@tevm/node';
import { createImpersonatedTx, createTxFromRLP, TransactionFactory, isBlobEIP4844Tx } from '@tevm/tx';
import { runTx } from '@tevm/vm';
import { createChain } from '@tevm/blockchain';
import { createStateManager } from '@tevm/state';
import { z, treeifyError } from 'zod';
import { EvmError } from '@tevm/evm';
import { Abi } from 'abitype/zod';
import { createMapDb } from '@tevm/receipt-manager';
import { BlockHeader, Block } from '@tevm/block';
import { createJsonRpcFetcher } from '@tevm/jsonrpc';
// src/anvil/anvilDealHandler.js
var forkAndCacheBlock = async (client, block, executeBlock = false) => {
client.logger.debug("Forking a new block based on block tag...");
const vm = await client.getVm().then((vm2) => vm2.deepCopy());
if (!client.forkTransport) {
throw new InternalError("Cannot forkAndCacheBlock without a fork url");
}
vm.stateManager = createStateManager({
...vm.evm.stateManager._baseState.options,
fork: {
transport: client.forkTransport,
blockTag: block.header.number
}
});
vm.evm.stateManager = vm.stateManager;
vm.blockchain = await createChain({
fork: {
transport: client.forkTransport,
blockTag: block.header.number
},
common: vm.common,
// TODO silent not being in this type is a bug
loggingLevel: (
/** @type {any}*/
client.logger.level
)
});
vm.evm.blockchain = vm.blockchain;
await Promise.all([vm.stateManager.ready(), vm.blockchain.ready()]);
if (executeBlock) {
const transactions = (
/** @type {import('@tevm/block').Block}*/
block.transactions
);
client.logger.debug({ count: transactions.length }, "Processing transactions");
await Promise.all(
transactions.map(async (tx, i) => {
client.logger.debug({ txNumber: i, tx }, "Processing transaction");
await vm.evm.shallowCopy().runCall(tx);
})
);
client.logger.debug("Finished processing block transactions and saving state root");
}
vm.stateManager.saveStateRoot(block.header.stateRoot, await vm.stateManager.dumpCanonicalGenesis());
return vm;
};
// src/Call/cloneVmWithBlock.js
var cloneVmWithBlockTag = async (client, block) => {
try {
client.logger.debug("Preparing vm to execute a call with block...");
const originalVm = await client.getVm();
if (client.forkTransport && !await originalVm.stateManager.hasStateRoot(block.header.stateRoot)) {
return await forkAndCacheBlock(client, block).catch((e) => {
return new ForkError(e instanceof Error ? e.message : "Unknown error", { cause: e });
});
}
const vm = await originalVm.deepCopy();
await vm.stateManager.setStateRoot(block.header.stateRoot);
return vm;
} catch (e) {
return new InternalError(e instanceof Error ? e.message : "unknown error", {
cause: (
/** @type {Error}*/
e
)
});
}
};
// src/internal/maybeThrowOnFail.js
var maybeThrowOnFail = (throwOnFail, result) => {
if (!throwOnFail) {
return (
/** @type {any}*/
result
);
}
if ((result?.errors?.length ?? 0) === 1) {
throw result.errors?.[0];
}
if ((result?.errors?.length ?? 0) > 1) {
throw new AggregateError(result?.errors ?? []);
}
return (
/** @type {any}*/
result
);
};
var callHandler = async (handler, data, secondParam) => {
if (typeof handler === "function") {
let hasCalledNext = false;
const next = () => {
hasCalledNext = true;
};
try {
let result;
if (secondParam !== void 0) {
result = handler(data, secondParam, next);
} else {
result = handler(data, next);
}
if (result instanceof Promise) {
await result;
}
if (!hasCalledNext) {
}
} catch (error) {
console.error("Error in event handler:", error);
}
}
};
var emitEvents = async (client, newBlocks, newReceipts, params = {}) => {
const { onBlock, onReceipt, onLog } = params;
for (const block of newBlocks) {
client.emit("newBlock", block);
await callHandler(onBlock, block);
const blockHash = bytesToHex(block.hash());
const receipts = newReceipts.get(blockHash);
if (!receipts) {
throw new Error(
`InternalError: Receipts not found for block hash ${blockHash} in mineHandler. This indicates a bug in tevm.`
);
}
for (const receipt of receipts) {
client.emit("newReceipt", receipt);
await callHandler(onReceipt, receipt, blockHash);
for (const log of receipt.logs) {
client.emit("newLog", log);
await callHandler(onLog, log, receipt);
}
}
}
};
var zBaseParams = z.object({
throwOnFail: z.boolean().optional().describe(
"If true the action handler will throw errors rather than returning errors an the `errors` property. Defaults to true."
)
}).describe("Properties shared across actions");
// src/Mine/zMineParams.js
var zMineParams = zBaseParams.extend({
blockCount: z.number().int().gte(0).optional(),
interval: z.number().int().gte(0).optional(),
onBlock: z.function().optional(),
onReceipt: z.function().optional(),
onLog: z.function().optional()
});
// src/Mine/validateMineParams.js
var validateMineParams = (action) => {
const errors = [];
const parsedParams = zMineParams.safeParse(action);
if (parsedParams.success === false) {
const formattedErrors = parsedParams.error.format();
formattedErrors._errors.forEach((error) => {
errors.push(new InvalidRequestError(error));
});
if (formattedErrors.blockCount) {
formattedErrors.blockCount._errors.forEach((error) => {
errors.push(new InvalidAddressError(error));
});
}
if (formattedErrors.interval) {
formattedErrors.interval._errors.forEach((error) => {
errors.push(new InvalidNonceError(error));
});
}
if (formattedErrors.throwOnFail) {
formattedErrors.throwOnFail._errors.forEach((error) => {
errors.push(new InvalidBalanceError(error));
});
}
}
return errors;
};
// src/Mine/mineHandler.js
var mineHandler = (client, options = {}) => async ({ throwOnFail = options.throwOnFail ?? true, tx, ...params } = {}) => {
switch (client.status) {
case "MINING": {
const err = new MisconfiguredClientError("Mining is already in progress");
return maybeThrowOnFail(throwOnFail, { errors: [err] });
}
case "INITIALIZING": {
await client.ready();
client.status = "MINING";
break;
}
case "SYNCING": {
const err = new MisconfiguredClientError("Syncing not currently implemented");
return maybeThrowOnFail(throwOnFail, { errors: [err] });
}
case "STOPPED": {
const err = new MisconfiguredClientError("Client is stopped");
return maybeThrowOnFail(throwOnFail, { errors: [err] });
}
case "READY": {
client.status = "MINING";
break;
}
default: {
const err = new UnreachableCodeError(client.status);
return maybeThrowOnFail(throwOnFail, { errors: [err] });
}
}
try {
client.logger.debug({ throwOnFail, ...params }, "mineHandler called with params");
const errors = validateMineParams(params);
if (errors.length > 0) {
return maybeThrowOnFail(throwOnFail, { errors });
}
const { interval = 1, blockCount = 1 } = params;
const newBlocks = [];
const newReceipts = /* @__PURE__ */ new Map();
client.logger.debug({ blockCount }, "processing txs");
const pool = await client.getTxPool();
const originalVm = await client.getVm();
const vm = await originalVm.deepCopy();
const receiptsManager = await client.getReceiptsManager();
for (let count = 0; count < blockCount; count++) {
const parentBlock = await vm.blockchain.getCanonicalHeadBlock();
let timestamp = Math.max(Math.floor(Date.now() / 1e3), Number(parentBlock.header.timestamp));
timestamp = count === 0 ? timestamp : timestamp + interval;
const blockBuilder = await vm.buildBlock({
parentBlock,
headerData: {
timestamp,
number: parentBlock.header.number + 1n,
// The following 2 are currently not supported
// difficulty: undefined,
// coinbase,
gasLimit: parentBlock.header.gasLimit,
baseFeePerGas: parentBlock.header.calcNextBaseFee()
},
blockOpts: {
// Proof of authority not currently supported
// cliqueSigner,
// proof of work not currently supported
//calcDifficultyFromHeader,
//ck
freeze: false,
setHardfork: false,
putBlockIntoBlockchain: false,
common: vm.common
}
});
const orderedTx = tx !== void 0 ? [
(() => {
const mempoolTx = pool.getByHash(tx);
pool.removeByHash(tx);
return mempoolTx;
})()
] : await pool.txsByPriceAndNonce({
baseFee: parentBlock.header.calcNextBaseFee()
});
let index = 0;
const blockFull = false;
const receipts = [];
while (index < orderedTx.length && !blockFull) {
const nextTx = (
/** @type {import('@tevm/tx').TypedTransaction}*/
orderedTx[index]
);
client.logger.debug({ hash: bytesToHex(nextTx.hash()) }, "new tx added");
const txResult = await blockBuilder.addTransaction(nextTx, {
skipBalance: true,
skipNonce: true,
skipHardForkValidation: true
});
receipts.push(txResult.receipt);
index++;
}
await vm.stateManager.checkpoint();
const createNewStateRoot = true;
await vm.stateManager.commit(createNewStateRoot);
const block = await blockBuilder.build();
await Promise.all([receiptsManager.saveReceipts(block, receipts), vm.blockchain.putBlock(block)]);
pool.removeNewBlockTxs([block]);
newBlocks.push(block);
newReceipts.set(bytesToHex(block.hash()), receipts);
const value = vm.stateManager._baseState.stateRoots.get(bytesToHex(block.header.stateRoot));
if (!value) {
return maybeThrowOnFail(throwOnFail, {
errors: [
new InternalError(
"InternalError: State root not found in mineHandler. This indicates a potential inconsistency in state management."
)
]
});
}
originalVm.stateManager.saveStateRoot(block.header.stateRoot, value);
}
originalVm.blockchain = vm.blockchain;
originalVm.evm.blockchain = vm.evm.blockchain;
receiptsManager.chain = vm.evm.blockchain;
await originalVm.stateManager.setStateRoot(hexToBytes(vm.stateManager._baseState.getCurrentStateRoot()));
await emitEvents(client, newBlocks, newReceipts, params);
return { blockHashes: newBlocks.map((b) => bytesToHex(b.hash())) };
} catch (e) {
return maybeThrowOnFail(throwOnFail, {
errors: [new InternalError(
/** @type {Error} */
e.message,
{ cause: e }
)]
});
} finally {
client.status = "READY";
}
};
// src/internal/getPendingClient.js
var getPendingClient = async (client) => {
const pendingClient = await client.deepCopy();
const txPool = await pendingClient.getTxPool();
const blockHashes = [];
while (txPool.txsInPool > 0) {
const { errors, blockHashes: newBlockHashes } = await mineHandler(pendingClient)({ throwOnFail: false });
if (errors !== void 0) {
return { errors };
}
blockHashes.push(...newBlockHashes);
}
return { pendingClient, blockHashes };
};
var zAddress = z.string().transform((val, ctx) => {
const regex = /^0x[a-fA-F0-9]{40}$/;
if (!regex.test(val)) {
ctx.addIssue({
code: "custom",
message: `Invalid Address ${val}`
});
}
return val;
}).describe("A valid ethereum address");
var hexRegex = /^0x[0-9a-fA-F]*$/;
var zHex = z.string().transform((value, ctx) => {
if (!hexRegex.test(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "value must be a hex string"
});
}
return (
/** @type {import('@tevm/utils').Hex}*/
value
);
}).describe("A hex string");
// src/internal/zod/zBlockParam.js
var zBlockParam = z.union([
z.literal("latest"),
z.literal("earliest"),
z.literal("pending"),
z.literal("safe"),
z.literal("finalized"),
z.bigint(),
z.number().transform((n) => BigInt(n)),
// Add number support with transformation
zHex
]);
// src/GetAccount/zGetAccountParams.js
var zGetAccountParams = zBaseParams.extend({
address: zAddress,
blockTag: zBlockParam.optional().describe('Block tag to execute call on. defaults to "latest"'),
returnStorage: z.boolean().optional().describe("If true will return storage. Defaults to false. This can be expensive")
}).describe("Params to create an account or contract");
// src/GetAccount/validateGetAccountParams.js
var validateGetAccountParams = (action) => {
const errors = [];
const parsedParams = zGetAccountParams.safeParse(action);
if (parsedParams.success === false) {
const formattedErrors = parsedParams.error.format();
if (formattedErrors.throwOnFail) {
for (const err of formattedErrors.throwOnFail._errors) {
errors.push(
new InvalidRequestError(`Invalid throwOnFail param. throwOnFail must be a boolean or not provided. ${err}`)
);
}
}
if (formattedErrors.returnStorage) {
for (const err of formattedErrors.returnStorage._errors) {
errors.push(
new InvalidRequestError(
`Invalid returnStorage param. returnStorage must be a boolean or not provided. ${err}`
)
);
}
}
if (formattedErrors.address) {
for (const err of formattedErrors.address._errors) {
errors.push(new InvalidAddressError(`Invalid address param. ${err}`));
}
}
if (formattedErrors.blockTag) {
for (const err of formattedErrors.blockTag._errors) {
errors.push(new InvalidRequestError(`Invalid blockTag param. ${err}`));
}
}
formattedErrors._errors.forEach((error) => {
errors.push(new InvalidRequestError(error));
});
}
return errors;
};
// src/GetAccount/getAccountHandler.js
var getAccountHandler = (client, options = {}) => async ({ throwOnFail = options.throwOnFail ?? true, ...params }) => {
const vm = await client.getVm();
const errors = validateGetAccountParams(params);
if (errors.length > 0) {
return maybeThrowOnFail(throwOnFail, {
errors,
address: params.address,
balance: 0n,
/**
* @type {`0x${string}`}
*/
storageRoot: "0x",
nonce: 0n,
/**
* @type {`0x${string}`}
*/
deployedBytecode: "0x",
/**
* @type {`0x${string}`}
*/
codeHash: "0x",
isContract: false,
isEmpty: true
});
}
const address = createAddress(params.address);
try {
if (params.blockTag === "pending") {
const mineResult = await getPendingClient(client);
if (mineResult.errors) {
return maybeThrowOnFail(throwOnFail, {
errors: mineResult.errors,
address: params.address,
balance: 0n,
/**
* @type {`0x${string}`}
*/
storageRoot: "0x",
nonce: 0n,
/**
* @type {`0x${string}`}
*/
deployedBytecode: "0x",
/**
* @type {`0x${string}`}
*/
codeHash: "0x",
isContract: false,
isEmpty: true
});
}
return getAccountHandler(mineResult.pendingClient, options)({ throwOnFail, ...params, blockTag: "latest" });
}
if (params.blockTag !== "latest" && params.blockTag !== void 0) {
const block = await vm.blockchain.getBlockByTag(params.blockTag);
const clonedVm = await cloneVmWithBlockTag(client, block);
if (clonedVm instanceof ForkError || clonedVm instanceof InternalError) {
return maybeThrowOnFail(throwOnFail, {
errors: [clonedVm],
address: params.address,
balance: 0n,
/**
* @type {`0x${string}`}
*/
storageRoot: "0x",
nonce: 0n,
/**
* @type {`0x${string}`}
*/
deployedBytecode: "0x",
/**
* @type {`0x${string}`}
*/
codeHash: "0x",
isContract: false,
isEmpty: true
});
}
return getAccountHandler(
{ ...client, getVm: () => Promise.resolve(clonedVm) },
options
)({ throwOnFail, ...params, blockTag: "latest" });
}
const res = await vm.stateManager.getAccount(address);
if (!res) {
return maybeThrowOnFail(throwOnFail, {
address: params.address,
balance: 0n,
/**
* @type {`0x${string}`}
*/
storageRoot: "0x",
nonce: 0n,
/**
* @type {`0x${string}`}
*/
deployedBytecode: "0x",
errors: [new AccountNotFoundError(`account ${params.address} not found`)],
/**
* @type {`0x${string}`}
*/
codeHash: "0x",
isContract: false,
isEmpty: true
});
}
const code = res?.codeHash !== void 0 ? bytesToHex(await vm.stateManager.getCode(address)) : "0x";
return {
// TODO some of these fields are not in the api and should be added to @tevm/actions
address: params.address,
balance: res.balance,
codeHash: bytesToHex(res.codeHash),
isContract: res.isContract(),
isEmpty: res.isEmpty(),
deployedBytecode: code,
nonce: res.nonce,
storageRoot: bytesToHex(res.storageRoot),
...params.returnStorage ? {
storage: Object.fromEntries(
Object.entries(await vm.stateManager.dumpStorage(address)).map(([key, value]) => [
`0x${key}`,
/** @type {import('../common/Hex.js').Hex}*/
value
])
)
} : {}
};
} catch (e) {
let err = e;
if (typeof e !== "object" || e === null || !("_tag" in e)) {
err = new InternalError("UnexpectedError in getAccountHandler", { cause: (
/** @type {any}*/
e
) });
}
errors.push(
/** @type any*/
err
);
return maybeThrowOnFail(throwOnFail, {
errors,
address: params.address,
balance: 0n,
/**
* @type {`0x${string}`}
*/
storageRoot: "0x",
/**
* @type {`0x${string}`}
*/
codeHash: "0x",
nonce: 0n,
/**
* @type {`0x${string}`}
*/
deployedBytecode: "0x",
isContract: false,
isEmpty: true
});
}
};
// src/internal/zod/zBytecode.js
var isValidEthereumBytecode = (bytecode) => {
const rawBytecode = bytecode.slice(2);
if (rawBytecode.length === 0 || rawBytecode.length % 2 !== 0) {
return false;
}
return true;
};
var zBytecode = zHex.refine(isValidEthereumBytecode, { message: "InvalidLength" }).describe("Valid bytecode");
var storageRootRegex = /^0x[0-9a-fA-F]{64}$/;
var zStorageRoot = z.string().transform((value, ctx) => {
if (!storageRootRegex.test(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Value must be a 32-byte hex string (64 hex characters with a 0x prefix)"
});
}
return value;
}).describe("Valid ethereum storage root");
// src/SetAccount/zSetAccountParams.js
var zSetAccountParams = zBaseParams.extend({
address: zAddress.describe("The ethereum address of the account"),
balance: z.bigint().nonnegative().optional().describe("The balance to give the account"),
nonce: z.bigint().nonnegative().optional().describe("The nonce to give the account"),
deployedBytecode: zBytecode.optional().describe("The contract bytecode to set at the account address as a >0 byte hex string"),
storageRoot: zStorageRoot.optional().describe("The storage root to set at the account address as a 32 byte hex strign"),
state: z.record(zHex, zHex).optional().describe("Overrides entire state with provided state"),
stateDiff: z.record(zHex, zHex).optional().describe("Patches the state with the provided state")
}).refine(
(data) => {
if (data.state && data.stateDiff) {
return false;
}
return true;
},
{ message: "Cannot have both state and stateDiff" }
).describe("Params to create an account or contract");
// src/SetAccount/validateSetAccountParams.js
var validateSetAccountParams = (action) => {
const errors = [];
const parsedParams = zSetAccountParams.safeParse(action);
if (parsedParams.success === false) {
const formattedErrors = parsedParams.error.format();
formattedErrors._errors.forEach((error) => {
errors.push(new InvalidRequestError(error));
});
if (formattedErrors.address) {
formattedErrors.address._errors.forEach((error) => {
errors.push(new InvalidAddressError(error));
});
}
if (formattedErrors.nonce) {
formattedErrors.nonce._errors.forEach((error) => {
errors.push(new InvalidNonceError(error));
});
}
if (formattedErrors.balance) {
formattedErrors.balance._errors.forEach((error) => {
errors.push(new InvalidBalanceError(error));
});
}
if (formattedErrors.deployedBytecode) {
formattedErrors.deployedBytecode._errors.forEach((error) => {
errors.push(new InvalidDeployedBytecodeError(error));
});
}
if (formattedErrors.storageRoot) {
formattedErrors.storageRoot._errors.forEach((error) => {
errors.push(new InvalidStorageRootError(error));
});
}
if (formattedErrors.state) {
formattedErrors.state._errors.forEach((error) => {
errors.push(new InvalidRequestError(error));
});
}
if (formattedErrors.stateDiff) {
formattedErrors.stateDiff._errors.forEach((error) => {
errors.push(new InvalidRequestError(error));
});
}
if (formattedErrors.throwOnFail) {
formattedErrors.throwOnFail._errors.forEach((error) => {
errors.push(new InvalidRequestError(error));
});
}
}
return errors;
};
// src/SetAccount/setAccountHandler.js
var setAccountHandler = (client, options = {}) => async (params) => {
const { throwOnFail = options.throwOnFail ?? true } = params;
const errors = validateSetAccountParams(params);
if (errors.length > 0) {
return maybeThrowOnFail(throwOnFail, { errors });
}
const address = createAddress(params.address);
const promises = [];
try {
const vm = await client.getVm();
const account = await getAccountHandler(client)({ ...params, throwOnFail: false });
if (account.errors?.length && !(account.errors[0] instanceof AccountNotFoundError)) {
client.logger.error({ errors: account.errors }, "there was an unexpected error getting account");
throw account.errors.length > 1 ? new AggregateError(account.errors) : account.errors[0];
}
const accountData = {
nonce: params.nonce ?? account?.nonce,
balance: params.balance ?? account?.balance
};
const storageRoot = (params.storageRoot && hexToBytes(params.storageRoot)) ?? (account?.storageRoot !== void 0 && account?.storageRoot !== "0x" ? hexToBytes(account.storageRoot) : void 0);
const codeHash = (params.deployedBytecode && hexToBytes(keccak256(params.deployedBytecode))) ?? (account?.deployedBytecode !== void 0 ? hexToBytes(keccak256(account.deployedBytecode)) : void 0);
if (storageRoot !== void 0) {
accountData.storageRoot = storageRoot;
}
if (codeHash !== void 0) {
accountData.codeHash = codeHash;
}
promises.push(vm.stateManager.putAccount(address, createAccount(accountData)));
if (params.deployedBytecode) {
promises.push(vm.stateManager.putCode(address, hexToBytes(params.deployedBytecode)));
}
if (params.state) {
await vm.stateManager.clearStorage(address);
}
const state = params.state ?? params.stateDiff;
if (state) {
for (const [key, value] of Object.entries(state)) {
promises.push(
vm.stateManager.putStorage(
address,
hexToBytes(
/** @type {import('@tevm/utils').Hex}*/
key,
{ size: 32 }
),
hexToBytes(value)
)
);
}
}
const results = await Promise.allSettled(promises);
for (const result of results) {
if (result.status === "rejected") {
errors.push(new InternalError("Unable to put storage", { cause: result.reason }));
}
}
if (errors.length > 0) {
return maybeThrowOnFail(throwOnFail, { errors });
}
await vm.stateManager.checkpoint();
await vm.stateManager.commit(false);
return {};
} catch (e) {
errors.push(new InternalError("Unexpected error setting account", { cause: e }));
return maybeThrowOnFail(throwOnFail, { errors });
}
};
// src/Contract/createScript.js
var createScript = async (client, code, deployedBytecode, to) => {
const scriptAddress = to ?? (() => {
const randomBigInt = BigInt(Math.floor(Math.random() * 1e15));
return getAddress(createContractAddress(createAddress(`0x${"6969".repeat(10)}`), randomBigInt).toString());
})();
const vm = await client.getVm();
if (deployedBytecode) {
const setAccountRes = await setAccountHandler(client)({
address: scriptAddress,
deployedBytecode,
throwOnFail: false
});
if (setAccountRes.errors) {
return {
errors: setAccountRes.errors
};
}
return {
address: scriptAddress
};
}
if (!code) {
return {
errors: [new InternalError("Cannot create script without code or deployedBytecode")]
};
}
const parentBlock = await vm.blockchain.getCanonicalHeadBlock();
const priorityFee = 0n;
const sender = createAddress(
/** @type {import('@tevm/utils').Address}*/
prefundedAccounts[0]
);
let _maxFeePerGas = parentBlock.header.calcNextBaseFee() + priorityFee;
const baseFeePerGas = parentBlock.header.baseFeePerGas ?? 0n;
if (_maxFeePerGas < baseFeePerGas) {
_maxFeePerGas = baseFeePerGas;
}
const dataFee = (() => {
let out = 0n;
for (const entry of hexToBytes(code) ?? []) {
out += entry === 0 ? 4n : 16n;
}
return out;
})();
const baseFee = (() => {
let out = dataFee;
const txFee = 21000n;
out += txFee;
if (vm.common.ethjsCommon.gteHardfork("homestead")) {
const txCreationFee = 32000n;
out += txCreationFee;
}
return out;
})();
const minimumGasLimit = baseFee + BigInt(4294967295);
const gasLimitWithExecutionBuffer = minimumGasLimit * 11n / 10n;
try {
const res = await runTx(vm)({
block: parentBlock,
tx: createImpersonatedTx({
maxFeePerGas: _maxFeePerGas,
maxPriorityFeePerGas: 0n,
gasLimit: gasLimitWithExecutionBuffer,
data: code,
impersonatedAddress: sender
}),
skipNonce: true,
skipBalance: true,
skipBlockGasLimitValidation: true,
skipHardForkValidation: true
});
if (res.execResult.exceptionError?.error) {
client.logger.error("Failed to create script because deployment of script bytecode failed");
throw new InvalidBytecodeError(res.execResult.exceptionError.error, {
cause: (
/** @type {any}*/
res.execResult.exceptionError
)
});
}
const deployedAddress = res.createdAddress;
if (!deployedAddress) {
return {
errors: [new InternalEvmError("Failed to create script")]
};
}
const account = await getAccountHandler(client)({
throwOnFail: false,
address: (
/** @type {import('@tevm/utils').Address}*/
deployedAddress.toString()
),
returnStorage: true
});
if (account.errors) {
return {
errors: account.errors
};
}
const setAccountRes = await setAccountHandler(client)({
...account,
address: scriptAddress,
throwOnFail: false,
stateDiff: account.storage ?? {},
deployedBytecode: account.deployedBytecode
});
if (setAccountRes.errors) {
return {
errors: setAccountRes.errors
};
}
await vm.stateManager.deleteAccount(deployedAddress);
return {
address: to ?? scriptAddress
};
} catch (e) {
return {
errors: [
/** @type any*/
e
]
};
}
};
var abi = parseAbi([
"function getL1GasUsed(bytes memory _data) public view returns (uint256)",
"function getL1Fee(bytes memory _data) external view returns (uint256)",
"function l1BaseFee() public view returns (uint256)",
"function blobBaseFee() public view returns (uint256)"
]);
var getL1FeeInformationOpStack = async (data, vm) => {
const opstackChain = (
/** @type {any}*/
vm.common
);
const serializedTx = serializeTransaction({
chainId: opstackChain.id,
data: bytesToHex(data ?? new Uint8Array()),
type: "eip1559"
});
const to = createAddress(opstackChain.contracts.gasPriceOracle.address);
const [l1GasUsed, l1Fee, l1BlobFee, l1BaseFee] = await Promise.all([
vm.evm.runCall({
to,
data: hexToBytes(
encodeFunctionData$1({
functionName: "getL1GasUsed",
args: [serializedTx],
abi
})
)
}),
vm.evm.runCall({
to,
data: hexToBytes(
encodeFunctionData$1({
functionName: "getL1Fee",
args: [serializedTx],
abi
})
)
}),
vm.evm.runCall({
to,
data: hexToBytes(
encodeFunctionData$1({
functionName: "blobBaseFee",
args: [],
abi
})
)
}),
vm.evm.runCall({
to,
data: hexToBytes(
encodeFunctionData$1({
functionName: "l1BaseFee",
args: [],
abi
})
)
})
]);
return {
l1GasUsed: decodeFunctionResult$1({
abi,
functionName: "getL1GasUsed",
data: bytesToHex(l1GasUsed.execResult.returnValue)
}),
l1Fee: decodeFunctionResult$1({
abi,
functionName: "getL1Fee",
data: bytesToHex(l1Fee.execResult.returnValue)
}),
l1BlobFee: decodeFunctionResult$1({
abi,
functionName: "blobBaseFee",
data: bytesToHex(l1BlobFee.execResult.returnValue)
}),
l1BaseFee: decodeFunctionResult$1({
abi,
functionName: "l1BaseFee",
data: bytesToHex(l1BaseFee.execResult.returnValue)
})
};
};
var callHandlerOpts = async (client, params) => {
const opts = {};
const vm = await client.getVm();
const block = await (async () => {
try {
if (params.blockTag === void 0) {
return vm.blockchain.blocksByTag.get("latest");
}
if (typeof params.blockTag === "bigint") {
return await vm.blockchain.getBlock(params.blockTag);
}
if (typeof params.blockTag === "string" && params.blockTag.startsWith("0x")) {
return await vm.blockchain.getBlock(hexToBytes(
/** @type {import('@tevm/utils').Hex}*/
params.blockTag
));
}
if (params.blockTag === "latest" || params.blockTag === "safe" || params.blockTag === "pending" || params.blockTag === "earliest" || params.blockTag === "finalized") {
return vm.blockchain.blocksByTag.get(
/** */
params.blockTag
);
}
return new InvalidBlockError(`Unknown blocktag ${params.blockTag}`);
} catch (e) {
return new UnknownBlockError(e instanceof Error ? e.message : `Unable to find block ${params.blockTag}`);
}
})();
if (block instanceof UnknownBlockError || block instanceof InvalidBlockError || block === void 0) {
return { errors: [block ?? new UnknownBlockError(`Unable to find block ${params.blockTag}`)] };
}
client.logger.debug({ block: block.header }, "Using block");
opts.block = block;
if (params.blockOverrideSet) {
client.logger.debug(params.blockOverrideSet, "callHandlerOpts: Detected a block override set");
const { header } = await vm.blockchain.getCanonicalHeadBlock();
opts.block = {
...opts.block,
header: {
// this isn't in the type but it needs to be here or else block overrides will fail
...{ stateRoot: block.header.stateRoot },
coinbase: params.blockOverrideSet.coinbase !== void 0 ? createAddress(params.blockOverrideSet.coinbase) : header.coinbase,
number: params.blockOverrideSet.number !== void 0 ? BigInt(params.blockOverrideSet.number) : header.number,
difficulty: header.difficulty,
prevRandao: header.prevRandao,
gasLimit: params.blockOverrideSet.gasLimit !== void 0 ? BigInt(params.blockOverrideSet.gasLimit) : header.gasLimit,
timestamp: params.blockOverrideSet.time !== void 0 ? BigInt(params.blockOverrideSet.time) : header.timestamp,
baseFeePerGas: params.blockOverrideSet.baseFee !== void 0 ? BigInt(params.blockOverrideSet.baseFee) : header.baseFeePerGas ?? BigInt(0),
getBlobGasPrice() {
if (params.blockOverrideSet?.blobBaseFee !== void 0) {
return BigInt(params.blockOverrideSet.blobBaseFee);
}
return header.getBlobGasPrice();
}
}
};
}
if (params.to) {
opts.to = createAddress(params.to);
}
if (params.data) {
opts.data = hexToBytes(params.data);
}
if (params.salt) {
opts.salt = hexToBytes(params.salt);
}
if (params.depth) {
opts.depth = params.depth;
}
if (params.blobVersionedHashes) {
opts.blobVersionedHashes = params.blobVersionedHashes;
}
if (params.selfdestruct) {
opts.selfdestruct = params.selfdestruct;
}
if (params.gasRefund) {
opts.gasRefund = BigInt(params.gasRefund);
}
if (params.gasPrice) {
opts.gasPrice = BigInt(params.gasPrice);
}
if (params.value) {
opts.value = BigInt(params.value);
}
const caller = params.caller || params.from || params.origin || (params.createTransaction || params.addToMempool || params.addToBlockchain ? "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" : `0x${"00".repeat(20)}`);
if (caller) {
opts.caller = createAddress(caller);
}
const origin = params.origin || params.from || params.caller || (params.createTransaction || params.addToMempool || params.addToBlockchain ? "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" : `0x${"00".repeat(20)}`);
if (origin) {
if (params.skipBalance !== void 0) {
opts.skipBalance = Boolean(params.skipBalance);
} else {
opts.skipBalance = caller === `0x${"00".repeat(20)}` && (params.createTransaction ?? params.addToMempool ?? params.addToBlockchain ?? false) === false;
}
opts.origin = createAddress(origin);
}
if (params.gas) {
opts.gasLimit = BigInt(params.gas);
}
if ((params.createTransaction || params.addToMempool || params.addToBlockchain) && opts.block !== await vm.blockchain.getCanonicalHeadBlock()) {
return { errors: [new InvalidParamsError("Creating transactions on past blocks is not currently supported")] };
}
return { data: opts };
};
var createEvmError = (error) => {
if (error instanceof BaseError) {
return (
/** @type {never}*/
error
);
}
const errorMessage = error?.error;
switch (errorMessage) {
case "stop": {
return new StopError(errorMessage, { cause: error });
}
case "revert": {
return new RevertError(errorMessage, { cause: error });
}
case "out of gas": {
return new OutOfGasError(errorMessage, { cause: error });
}
case "invalid opcode": {
return new InvalidOpcodeError(errorMessage, { cause: error });
}
case "stack overflow": {
return new StackOverflowError(errorMessage, { cause: error });
}
case "stack underflow": {
return new StackUnderflowError(errorMessage, { cause: error });
}
case "invalid JUMP": {
return new InvalidJumpError(errorMessage, { cause: error });
}
case "value out of range": {
return new OutOfRangeError(errorMessage, { cause: error });
}
case "kzg proof invalid": {
return new InvalidProofError(errorMessage, { cause: error });
}
// @ts-expect-error - This error message is deprecated in ethereumjs v10
case "attempting to AUTHCALL without AUTH set": {
return new AuthCallUnsetError(errorMessage, { cause: error });
}
case "internal error": {
return new InternalError(errorMessage, { cause: error });
}
case "kzg inputs invalid": {
return new InvalidKzgInputsError(errorMessage, { cause: error });
}
case "value overflow": {
return new ValueOverflowError(errorMessage, { cause: error });
}
// @ts-expect-error - This error message is deprecated in ethereumjs v10
case "invalid JUMPSUB": {
return new InvalidJumpSubError(errorMessage, { cause: error });
}
case "create collision": {
return new CreateCollisionError(errorMessage, { cause: error });
}
// @ts-expect-error - This error message is deprecated in ethereumjs v10
case "invalid BEGINSUB": {
return new InvalidBeginSubError(errorMessage, { cause: error });
}
case "refund exhausted": {
return new RefundExhaustedError(errorMessage, { cause: error });
}
// @ts-expect-error - This error message is deprecated in ethereumjs v10
case "invalid RETURNSUB": {
return new InvalidReturnSubError(errorMessage, { cause: error });
}
case "kzg commitment does not match versioned hash": {
return new InvalidCommitmentError(errorMessage, { cause: error });
}
case "invalid EOF format": {
return new InvalidEofFormatError(errorMessage, { cause: error });
}
case "static state change": {
return new StaticStateChangeError(errorMessage, { cause: error });
}
case "code store out of gas": {
return new CodeStoreOutOfGasError(errorMessage, { cause: error });
}
case "insufficient balance": {
return new InsufficientBalanceError(errorMessage, { cause: error });
}
case "invalid input length": {
return new InvalidInputLengthError(errorMessage, { cause: error });
}
case "input is empty": {
return new BLS12381InputEmptyError(errorMessage, { cause: error });
}
case "initcode exceeds max initcode size": {
return new InitcodeSizeViolationError(errorMessage, { cause: error });
}
case "invalid bytecode deployed": {
return new InvalidBytecodeResultError(errorMessage, { cause: error });
}
case "code size to deposit exceeds maximum code size": {
return new CodeSizeExceedsMaximumError(errorMessage, { cause: error });
}
case "fp point not in field": {
return new BLS12381FpNotInFieldError(errorMessage, { cause: error });
}
case "point not on curve": {
return new BLS12381PointNotOnCurveError(errorMessage, { cause: error });
}
default: {
return new InternalError(errorMessage || "Unknown error", {
cause: error
});
}
}
};
// src/Call/callHandlerResult.js
var callHandlerResult = (evmResult, txHash, trace, accessList) => {
const out = {
rawData: bytesToHex(
/** @type {any} */
evmResult.execResult.returnValue
),
executionGasUsed: (
/** @type {any} */
evmResult.execResult.executionGasUsed
)
};
if (trace) {
out.trace = trace;
}
if (evmResult.totalGasSpent) {
out.totalGasSpent = evmResult.totalGasSpent;
}
if (evmResult.minerValue) {
out.minerValue = evmResult.minerValue;
}
if (evmResult.blobGasUsed) {
out.blobGasUsed = evmResult.blobGasUsed;
}
if (evmResult.amountSpent) {
out.amountSpent = evmResult.amountSpent;
}
if (accessList && evmResult.preimages) {
out.preimages = Object.fromEntries(
[...evmResult.preimages.entries()].map(([key, value]) => [key, bytesToHex(value)])
);
}
if (accessList) {
out.accessList = /** @type {Record<import('@tevm/utils').Address, Set<import('@tevm/utils').Hex>>} */
Object.fromEntries(
[...accessList.entries()].map(([address, storageKeys]) => {
const hexKeys = new Set([...storageKeys].map((key) => `0x${key}`));
return [`0x${address}`, hexKeys];
})
);
}
if (txHash) {
out.txHash = txHash;
}
if (
/** @type {any} */
evmResult.execResult.gasRefund
) {
out.gasRefund = evmResult.gasRefund ?? /** @type {any} */
evmResult.execResult.gasRefund;
}
if (
/** @type {any} */
evmResult.execResult.selfdestruct
) {
out.selfdestruct = new Set(
[.../** @type {any} */
evmResult.execResult.selfdestruct].map((address) => getAddress(address))
);
}
if (
/** @type {any} */
evmResult.execResult.gas
) {
out.gas = /** @type {any} */
evmResult.execResult.gas;
}
if (
/** @type {any} */
evmResult.execResult.logs
) {
out.logs = /** @type {any} */
evmResult.execResult.logs.map(
(log) => {
const [address, topics, data] = log;
return {
address: getAddress(toHex(address)),
topics: topics.map((topic) => toHex(topic)),
data: toHex(data)
};
}
);
}
if (
/** @type {any} */
evmResult.execResult.runState
) ;
if (
/** @type {any} */
evmResult.execResult.blobGasUsed
) {
out.blobGasUsed = /** @type {any} */
evmResult.execResult.blobGasUsed;
}
if (
/** @type {any} */
evmResult.execResult.exceptionError
) {
if (out.errors === void 0) {
out.errors = [];
}
out.errors.push(createEvmError(
/** @type {any} */
evmResult.execResult.exceptionError
));
}
if (
/** @type {any} */
evmResult.execResult.createdAddresses
) {
out.createdAddresses = new Set([.../** @type {any} */
evmResult.execResult.createdAddresses].map(getAddress));
}
if (
/** @type {any} */
evmResult.createdAddress
) {
out.createdAddress = getAddress(
/** @type {any} */
evmResult.createdAddress.toString()
);
}
return out;
};
var evmInputToImpersonatedTx = (client) => {
return async (evmInput, maxFeePerGas, maxPriorityFeePerGas) => {
const vm = await client.getVm();
const parentBlock = await vm.blockchain.getCanonicalHeadBlock();
const priorityFee = 0n;
const sender = evmInput.origin ?? evmInput.caller ?? createAddress(`0x${"00".repeat(20)}`);
const txPool = await client.getTxPool();
const txs = await txPool.getBySenderAddress(sender);
const nonce = (await vm.stateManager.getAccount(sender) ?? { nonce: 0n }).nonce + BigInt(txs.length);
client.logger.debug({ nonce, sender: sender.toString() }, "creating tx with nonce");
let _maxFeePerGas = parentBlock.header.calcNextBaseFee() + priorityFee;
const baseFeePerGas = parentBlock.header.baseFeePerGas ?? 0n;
if (_maxFeePerGas < baseFeePerGas) {
_maxFeePerGas = baseFeePerGas;
}
if (maxPriorityFeePerGas && _maxFeePerGas < maxPriorityFeePerGas) {
_maxFeePerGas = maxPriorityFeePerGas;
}
return createImpersonatedTx(
{
impersonatedAddress: sender,
nonce,
// just set to block max for now
gasLimit: parentBlock.header.gasLimit,
maxFeePerGas: maxFeePerGas ?? _maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas ?? 0n,
...evmInput.to !== void 0 ? { to: evmInput.to } : {},
...evmInput.data !== void 0 ? { data: evmInput.data } : {},
...evmInput.value !== void 0 ? { value: evmInput.value } : {},
gasPrice: null
},
{
allowUnlimitedInitCodeSize: false,
common: vm.common.ethjsCommon,
// we don't want to freeze because there is a hack to set tx.hash when building a block
freeze: false
}
);
};
};
var runCallWithTrace = async (vm, logger, params, lazilyRun = false) => {
const trace = {
gas: 0n,
returnValue: "0x0",
failed: false,
structLogs: []
};
vm.evm.events?.on("step", async (step, next) => {
logger.debug(step, "runCallWithTrace: new evm step");
trace.structLogs.push({
pc: step.pc,
op: step.opcode.name,
gasCost: BigInt(step.opcode.fee) + (step.opcode.dynamicFee ?? 0n),
gas: step.gasLeft,
depth: step.depth,
stack: step.stack.map((code) => numberToHex(code))
});
next?.();
});
vm.evm.events?.on("afterMessage", (data, next) => {
logger.debug(data.execResult, "runCallWithTrace: new message result");
if (data.execResult.exceptionError !== void 0 && trace.structLogs.length > 0) {
const nextLog = trace.structLogs[trace.structLogs.length - 1];
invariant(nextLog, new DefensiveNullCheckError("No structLogs to mark as error"));
Object.assign(nextLog, {
error: data.execResult.exceptionError
});
}
next?.();
});
if (lazilyRun) {
return (
/** @type any*/
{ trace }
);
}
const runCallResult = await vm.evm.runCall(params);
logger.debug(runCallResult, "runCallWithTrace: evm run call complete");
trace.gas = runCallResult.execResult.executionGasUsed;
trace.failed = runCallResult.execResult.exceptionError !== void 0;
trace.returnValue = bytesToHex(runCallResult.execResult.returnValue);
return {
...runCallResult,
trace
};
};
var prefetchStorageFromAccessList = async (client, accessList) => {
if (!accessList || accessList.size === 0) return;
const vm = await client.getVm();
const stateManager = vm.stateManager;
const prefetchPromises = [];
for (const [address, storageKeys] of accessList.entries()) {
if (storageKeys.size === 0) continue;
const addressObj = createAddress(address.startsWith("0x") ? address : `0x${address}`);
for (const storageKey of storageKeys) {
const hexKey = (
/** @type {`0x${string}`} */
storageKey.startsWith("0x") ? storageKey : `0x${storageKey}`
);
const keyBytes = hexToBytes$1(hexKey, { size: 32 });
prefetchPromises.push(
stateManager.getStorage(addressObj, keyBytes).catch((error) => {
client.logger.debug(
{
error,
address: address.startsWith("0x") ? address : `0x${address}`,
storageKey: storageKey.startsWith("0x") ? storageKey : `0x${storageKey}`
},
"Error prefetching storage slot from access list"
);
})
);
}
}
await Promise.all(prefetchPromises);
client.logger.debug(
{ accessListSize: accessList.size, totalStorageSlotsPreloaded: prefetchPromises.length },
"Prefetched storage slots from access list"
);
};
// src/internal/setupPrefetchProxy.js
var setupPrefetchProxy = async (client, accessList) => {
if (!client.forkTransport || !accessList || accessList.size === 0) return;
let hasPrefetched = false;
const originalRequest = client.forkTransport.request.bind(client.forkTransport);
client.forkTransport.request = async (request) => {
if (!hasPrefetched && (request.method === "eth_getStorageAt" || request.method === "eth_getProof")) {
client.logger.debug({ method: request.method }, "First storage request detected, triggering prefetch");
hasPrefetched = true;
prefetchStorageFromAccessList(client, accessList).catch((error) => {
client.logger.error({ e