Compiled artifact
compile(script) returns a frozen CompiledEvsScript — everything you need to execute,
inspect, and debug a script. This page documents every property and method. For how to run the
artifact against a chain, see execution; for the debugging workflow, see
errors and debugging.
import { compile, evscript, t } from '@maxencerb/evs';import { createPublicClient, http } from 'viem';
const erc20 = [ { type: 'function', name: 'totalSupply', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint256' }], },] as const;
const script = evscript({ name: 'totalSupplyOf', args: [t.address] }, (s, token) => { const supply = s.read({ address: token, abi: erc20, functionName: 'totalSupply' }); return s.return({ supply });});
const compiled = compile(script);
const client = createPublicClient({ transport: http('https://eth.example.com') });const result = await client.readContract({ ...compiled.toViem(), functionName: 'totalSupplyOf', args: ['0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'],});// result is typed { supply: bigint } — inferred from the literal-typed abiconst supply: bigint = result.supply;Surface
Section titled “Surface”| Member | Type | Description |
|---|---|---|
abi |
ScriptAbi<name, args, ret, errs> |
Literal-typed ABI value: [function, EvsInvalidCalldata, EvsDecodeError, ...declared errors] |
runtimeBytecode |
Hex |
Deployed-form bytecode; at most 24,576 bytes (EIP-170), enforced |
initBytecode |
Hex |
The 10-byte init wrapper followed by the runtime — creation bytecode |
sourceMap |
SourceMap |
PC-to-source segments, revert sites, labels |
ir |
ScriptIr |
The frozen recorded IR (same object as script.ir) |
options |
Readonly<Required<CompileOptions>> |
The resolved options the artifact was built with |
toViem(o?) |
overloaded | Execution shapes to spread into viem’s readContract |
disassemble() |
Disassembly |
Annotated instruction listing over runtimeBytecode |
explainRevert(data) |
RevertExplanation |
Decodes a revert payload against this script’s source map |
The artifact object is Object.freezed. script.compile(options) is sugar for
compile(script, options) — see evscript for compile, CompileOptions,
and their defaults.
abi — the ScriptAbi
Section titled “abi — the ScriptAbi”The ABI is a value with a literal type: one view function named after the script, followed by
the two evs error entries, then one error entry per custom error declared in the def’s
errors: [...] (in declaration order; none for the script below). The function has exactly one output — a tuple named result whose
components are the keys of your s.return({ ... }) record. Because every component is named,
viem infers an object, not a positional array.
For the totalSupplyOf script above, compiled.abi is:
const abi = [ { type: 'function', name: 'totalSupplyOf', stateMutability: 'view', inputs: [{ name: 'arg0', type: 'address' }], // args are auto-named arg{i} — positional labels outputs: [ { name: 'result', type: 'tuple', components: [{ name: 'supply', type: 'uint256' }], }, ], }, { type: 'error', name: 'EvsInvalidCalldata', inputs: [] }, { type: 'error', name: 'EvsDecodeError', inputs: [{ name: 'site', type: 'uint256' }] },] as const;The runtime ABI array is the encode/decode source of truth: input order is the args tuple
order, and component order is the insertion order of the s.return record. The same ABI exists
pre-compile as script.abi, so a codegen failure can never corrupt the typed surface. The
ScriptAbi type is exported; see types.
runtimeBytecode and initBytecode
Section titled “runtimeBytecode and initBytecode”runtimeBytecodeis the deployed form — what actually executes. Compilation fails withEvsCompileError(codeCOMPILE_LIMIT) if it exceeds the EIP-170 limit of 24,576 bytes; the message includes a per-region size breakdown (dispatcher, body, fns, tails, data segments, plustrampoline/codecswhen present).initBytecodeiswrapper ++ runtimeBytecode, where the wrapper is a locked 10-byte init stub that CODECOPYs everything after itself and returns it as the deployed code:
61 RRRR PUSH2 len ; runtime length, big-endian80 DUP160 0A PUSH1 0x0A ; the wrapper is 10 bytes; runtime starts right after5F PUSH0 ; paris target: 3D (RETURNDATASIZE — zero in an init frame)39 CODECOPY ; mem[0..len) = code[10..10+len)5F PUSH0 ; paris target: 3DF3 RETURN ; return the runtime as the deployed codeThe init form exists because viem’s deployless code call parameter expects creation
bytecode — passing runtime bytecode fails with an empty revert that carries no diagnostic data. The artifact deliberately has no field named
code or bytecode; the only place a code key appears is in the toViem() results, where it
is always the correct form for that mode.
toViem() — execution shapes
Section titled “toViem() — execution shapes”Five overloads, two modes. Every result spreads directly into viem’s readContract:
| Call | Returns |
|---|---|
toViem() |
{ abi, code: initBytecode } — deployless (default) |
toViem({ mode: 'deployless' }) |
same as the no-argument form |
toViem({ mode: 'stateOverride', address? }) |
{ abi, address, stateOverride: [{ address, code: runtimeBytecode }] } |
toViem({ mode: 'stateOverride', sender, address? }) |
{ abi, address: sender, stateOverride: [{ address: sender, code: runtimeBytecode }], account: sender } — sender mode: sub-call targets see msg.sender = sender; address, if given, must equal sender |
toViem({ mode, address?, sender? }) with mode: ToViemMode (optional, or ToViemMode | undefined) |
the union of the shapes above — for a mode chosen at run time; an unset mode is deployless; address / sender apply only when mode is 'stateOverride' |
import { compile, DEFAULT_SCRIPT_ADDRESS, evscript } from '@maxencerb/evs';
const script = evscript({ name: 'blockNow', args: [] }, (s) => { return s.return({ ts: s.env('timestamp'), height: s.env('blocknumber') });});const compiled = compile(script);
// deployless: plain two-parameter eth_call, maximum RPC compatibilityconst deployless = compiled.toViem();const sameInit: boolean = deployless.code === compiled.initBytecode; // true — creation bytecode
// state override: runtime bytecode installed at a deterministic addressconst overridden = compiled.toViem({ mode: 'stateOverride' });const atDefault: boolean = overridden.address === DEFAULT_SCRIPT_ADDRESS; // trueconst installsRuntime: boolean = overridden.stateOverride[0].code === compiled.runtimeBytecode; // trueWhen the mode is only known at run time — a config value, or a provider probe, since not every
provider supports state overrides — type it as the exported ToViemMode
('deployless' | 'stateOverride') and pass it straight through. The result is the union of the
shapes, which still spreads into readContract with full return inference:
import { compile, evscript, t, type ToViemMode } from '@maxencerb/evs';import { createPublicClient, http } from 'viem';import { mainnet } from 'viem/chains';
const double = compile( evscript({ name: 'double', args: [t.uint256] }, (s, x) => s.return({ y: x.mul(2n) })),);const client = createPublicClient({ chain: mainnet, transport: http() });
async function run(mode: ToViemMode): Promise<bigint> { const { y } = await client.readContract({ ...double.toViem({ mode }), functionName: 'double', args: [21n], }); return y;}The mode may also be optional or ToViemMode | undefined — a config field left unset to get the
default — and an unset mode is deployless, as for toViem(). A literal mode keeps its own
overload and precise shape; { mode: 'deployless', address } is still a type error, and so is an
object with no mode key at all (call toViem()). Since the catch-all is the last overload,
ReturnType<CompiledEvsScript['toViem']> names that union.
State-override mode accepts an optional address (viem’s Address type) to place the script
somewhere else; it requires a provider that supports the third eth_call parameter. The two
modes run the script in different execution frames, which matters for s.env('caller') and
s.env('address') — see execution for the full comparison, block pinning,
and the ENV_FRAME_DEPENDENT warning. Deployless mode runs as a contract creation, so its result
is capped at 24,576 bytes (and must not start with 0xEF), its payload — initBytecode plus
the encoded args — at about 48.6 KB, and it costs extra gas; state-override mode has none of
these limits. See deployless limits below for the checks evs provides, and
the execution guide for the gas cost and workarounds.
DEFAULT_SCRIPT_ADDRESS
Section titled “DEFAULT_SCRIPT_ADDRESS”0xcD360FfAC9818c4396Aa6F4807EBfA72C4B3f530Exported as a constant (type Address). It is the last 20 bytes of keccak256("evs.script") —
an address with no code, storage, or balance on any major chain, so overriding it is safe.
Deployless limits
Section titled “Deployless limits”A deployless call has no to, so the node runs it as a contract creation: viem’s 398-byte
wrapper contract deploys initBytecode, calls it, and returns the script’s result as its own
deployed code. Three contract-creation rules therefore apply in deployless mode, and none of them
applies in state-override mode:
| Limit | Rule | Fails when |
|---|---|---|
DEPLOYLESS_MAX_RESULT_BYTES = 24,576 |
EIP-170 (code size) | The ABI-encoded result is larger. A uint256[] result takes 96 + 32·n bytes, so 765 elements fit and 766 do not |
no leading 0xEF |
EIP-3541 | The result’s first byte is 0xEF. Only a result whose first returned value is a static bytesN, uint256 or int256 (directly or as the first member of a static struct or fixed array) can start with it: a hash fails about 1 time in 256, while a uint256 needs at least 0xEF·2^248 and an int256 must be below -2^252, which amounts and counts never reach |
DEPLOYLESS_MAX_DATA_BYTES = 49,152 |
EIP-3860 (initcode size) | viem’s creation data is larger: the wrapper, initBytecode and the ABI-encoded call including its args |
compile() reports the result-shape cases it can see through onDiagnostic:
DEPLOYLESS_RESULT_PREFIX and DEPLOYLESS_RESULT_SIZE (see
diagnostics). The args
side depends on each call, so measure it with deploylessDataSize(compiled, calldata), which does
the arithmetic without sending anything:
import { compile, DEPLOYLESS_MAX_DATA_BYTES, deploylessDataSize, evscript, explainDeploylessError, t,} from '@maxencerb/evs';import { createPublicClient, encodeFunctionData, http, type Address } from 'viem';
const script = evscript({ name: 'count', args: [t.array(t.address)] }, (s, list) => { return s.return({ n: list.length() });});const compiled = compile(script);const client = createPublicClient({ transport: http('https://eth.example.com') });
declare const tokens: Address[];const calldata = encodeFunctionData({ abi: compiled.abi, functionName: 'count', args: [tokens] });const mode = deploylessDataSize(compiled, calldata) <= DEPLOYLESS_MAX_DATA_BYTES ? compiled.toViem() : compiled.toViem({ mode: 'stateOverride' });
try { await client.readContract({ ...mode, functionName: 'count', args: [tokens] });} catch (error) { const limit = explainDeploylessError(error); if (limit === undefined) throw error; // not a deployless creation limit limit.kind; // 'result-starts-with-ef' | 'result-too-large' | 'data-too-large' limit.message; // what happened, and that toViem({ mode: 'stateOverride' }) avoids it limit.nodeMessage; // the node's own text, e.g. 'max code size exceeded: code size 24608 limit 24576'}The node rejects these calls with an error viem often shows only as “Missing or invalid
parameters” or “Transaction creation failed”. explainDeploylessError searches the caught error’s
cause chain for the node’s text — max code size exceeded and invalid code: must not begin with 0xef (geth and the nodes that reuse its wording), max initcode size exceeded (geth and
anvil), CreateContractSizeLimit and CreateContractStartingWithEF (revm’s error names, which
anvil reports), and CreateInitCodeSizeLimit (revm’s name for the initcode case, matched in case
another revm-based node reports it) — and returns undefined for anything else. size holds the rejected size when the node reports it (geth does)
and limit the exceeded cap.
EVS_ERROR_ABI
Section titled “EVS_ERROR_ABI”The two evs-owned error entries, exported as a standalone as const ABI and appended verbatim
to every script’s abi (entries 1 and 2):
import { EVS_ERROR_ABI } from '@maxencerb/evs';
EVS_ERROR_ABI[0]; // { type: 'error', name: 'EvsInvalidCalldata', inputs: [] }EVS_ERROR_ABI[1]; // { type: 'error', name: 'EvsDecodeError', inputs: [{ name: 'site', type: 'uint256' }] }| Error | Selector | Raised when |
|---|---|---|
EvsInvalidCalldata() |
0xf43fed56 |
The calldata does not match the script’s function (wrong selector, truncated calldata, malformed dynamic arguments) |
EvsDecodeError(uint256 site) |
0x20cf27b7 |
A strict s.read got returndata that fails to decode; site identifies the recorded call site |
Because both entries are in every script’s ABI, viem decodes these reverts into named errors out
of the box. The same holds for the custom errors a script declares and throws with
s.throw: they are part of the script ABI, and explainRevert
reports them as kind 'script-error'. All other reverts (callee Error(string), callee custom
errors, panics) bubble through the script byte-exactly — decode those against the callee’s ABI;
see calls.
disassemble() — Disassembly
Section titled “disassemble() — Disassembly”import { compile, evscript, t } from '@maxencerb/evs';
const script = evscript({ name: 'one', args: [] }, (s) => { return s.return({ v: s.lit(t.uint256, 1n) });});const dis = compile(script).disassemble();
const listing: string = dis.format(); // listing with labels, jump targets and notesfor (const line of dis.lines) { line.pc; // byte offset line.mnemonic; // opcode name line.raw; // exact bytes of this instruction}Disassembly is { lines, format() }. Each element of lines has:
| Field | Type | Meaning |
|---|---|---|
pc |
number |
Byte offset of the instruction |
raw |
Hex |
Exact bytes — concatenating every raw reproduces the input byte-for-byte |
mnemonic |
string |
Opcode name; non-opcode bytes (data segments) appear as UNKNOWN_0x<byte> |
pushValue? |
Hex |
Immediate of a PUSH1–PUSH32 |
targetLabel? |
string |
Label whose pc matches a small push immediate |
label? |
string |
Label defined at this pc |
note? |
string |
Codegen annotation (for example free-ptr init) |
The first lines of the totalSupplyOf artifact from the top of this page
(format(), real compiler output):
0x0000 60c0 PUSH1 0xc0 ; frameEnd0x0002 6040 PUSH1 0x400x0004 52 MSTORE ; free-ptr init0x0005 6004 PUSH1 0x040x0007 36 CALLDATASIZE0x0008 10 LT0x0009 6100ba PUSH2 0x00ba → @badcd0x000c 57 JUMPI0x000d 5f PUSH00x000e 35 CALLDATALOAD0x000f 60e0 PUSH1 0xe00x0011 1c SHR0x0012 63cf9530d0 PUSH4 0xcf9530d0 ; selector totalSupplyOf(address)0x0017 14 EQ0x0018 610020 PUSH2 0x0020 → @main0x001b 57 JUMPIA push whose value happens to coincide with some label’s pc gets a speculative → @label
annotation; only PUSH2 immediates feeding a JUMP/JUMPI are actual jump targets.
sourceMap and lookupPc
Section titled “sourceMap and lookupPc”import { compile, evscript, lookupPc, t } from '@maxencerb/evs';
const script = evscript({ name: 'one', args: [] }, (s) => { return s.return({ v: s.lit(t.uint256, 1n) });});const compiled = compile(script);
const hit = lookupPc(compiled.sourceMap, 0);if (hit !== undefined) { hit.note; // optional codegen annotation}
for (const site of compiled.sourceMap.sites) { site.kind; // 'panic' | 'decode' | 'call' | 'stmt' site.detail; // human-readable, e.g. 'checked add args.x + 1 (uint256) — Panic 0x11' site.panicCodes; // 'panic' sites only, e.g. [0x11] site.sendsValue; // true on an s.call / s.simulate site that sends a `value`}SourceMap fields:
| Field | Shape | Notes |
|---|---|---|
version |
1 |
Format version |
segments |
readonly { pc, len, note? }[] |
Sorted by pc, non-overlapping; together they cover every emitted code byte. Each is a maximal run of consecutive instructions sharing one note (or none), so neighbouring segments always differ |
sites |
readonly { id, kind, detail, panicCodes?, sendsValue? }[] |
Revert-attribution sites; kind is 'panic' | 'decode' | 'call' | 'stmt'; id is a number; panicCodes (panic sites only) lists the Panic(uint256) codes the site can raise; sendsValue is true on an s.call / s.simulate site (or its try* form) whose value is not a literal 0 — paid from the script’s own balance; detail is display text |
labels |
readonly { pc, name }[] |
Label name per jump-destination pc |
lookupPc(map, pc) binary-searches the segments and returns { note?: string }, or
undefined when no segment covers the pc. The sites table powers explainRevert and the
EvsDecodeError(site) round trip.
A detail names the site’s operands so that sites of the same kind can be told apart: an
argument as args.<name> (args.arg0 for an unnamed one), a literal by its value, a call
result by its call (s.read(totalSupply)), and any other value as #id, the id its handle
prints (console.log(x) shows Expr<uint256> #12). A checked operation whose check can never
fire (a division by a nonzero literal, a multiplication by a literal 0 or 1, a widening
conversion, …) is a 'stmt' site, never a panic candidate.
explainRevert(data) — RevertExplanation
Section titled “explainRevert(data) — RevertExplanation”Decodes a raw revert payload (the hex data from a failed eth_call) against this script’s IR
and source map:
import { compile, evscript, t } from '@maxencerb/evs';
const script = evscript({ name: 'addOne', args: [t.uint256] }, (s, x) => { return s.return({ y: s.add(x, 1n) });});const compiled = compile(script);
const explanation = compiled.explainRevert( '0x4e487b710000000000000000000000000000000000000000000000000000000000000011',);explanation.kind; // 'panic'explanation.panicCode; // 17n (0x11 — arithmetic overflow or underflow)explanation.message; // quoted belowOutput (the candidate names the s.add site by what it is, its operands and its site id):
Panic(0x11): arithmetic overflow or underflow — 1 candidate site(s) in this script: checked add args.arg0 + 1 (uint256) — Panic 0x11 (site 1)The result shape (site ids are plain numbers):
interface RevertExplanation { kind: | 'panic' | 'evs-decode' | 'evs-invalid-calldata' | 'error-string' | 'script-error' | 'custom' | 'empty'; message: string; panicCode?: bigint; // kind 'panic' only errorName?: string; // kind 'script-error' only: the declared error's name errorArgs?: Readonly<Record<string, unknown>>; // kind 'script-error' only: name-keyed decoded args site?: { id: number; detail: string }; // kind 'evs-decode' only candidateSites?: readonly { id: number; detail: string }[]; // 'panic', 'error-string', 'custom', 'empty', malformed 'script-error' raw: `0x${string}`;}kind |
Payload | Extras |
|---|---|---|
'panic' |
Panic(uint256) (selector 0x4e487b71, 36 bytes) |
panicCode, plus candidateSites — every site in this script whose code can raise that panic code; with none, the message names the strict call sites it was bubbled through |
'evs-decode' |
EvsDecodeError(uint256) |
site only for the exact 36-byte payload this compiler emits whose id resolves to a returndata-decode site in this artifact’s source map; a longer payload that still decodes is 'evs-decode' with no site. For a sendsValue site of a strict s.simulate, or of an s.call with revertReturns, the message adds that the script’s balance may have been below the value (an unfunded script fails those sites this way; a plain strict s.call reverts empty instead) |
'evs-invalid-calldata' |
EvsInvalidCalldata() (exactly 4 bytes) |
Message names the expected function signature |
'script-error' |
A custom error declared in the script’s errors |
errorName and errorArgs (name-keyed decoded args) — thrown by this script’s s.throw; when the args do not decode, no errorArgs and candidateSites as for 'error-string' (s.throw cannot produce it) |
'error-string' |
Error(string) |
Bubbled verbatim from a callee; message quotes the reason; candidateSites — the strict call sites it can have come through |
'custom' |
Any other selector (or a payload shorter than 4 bytes) | Decode it against the callee’s ABI; candidateSites as for 'error-string' |
'empty' |
Zero-length returndata | A bare revert()/require(false) bubbled (candidateSites as for 'error-string'), a strict s.call whose value the script’s balance could not cover (the message names those sendsValue sites), or the frame failed without a reason: out of gas, or an opcode the node does not support at that block — the message names the fork-gated opcodes (PUSH0, MCOPY) the runtime uses |
A callee’s revert can only enter the script through a strict call site — s.read, s.call
or s.simulate, which bubble it verbatim. try* sites swallow it, and a revertReturns site
decodes it as its value, so neither is ever listed. When a script has no strict call site, a
payload only a callee can produce did not come from this artifact, and the message says so.
explainRevert and decodeScriptError classify a
payload with the same rules, so they always agree on what it is.
The panic-code meaning table (including codes evs itself never emits but callees can bubble) is in diagnostics.
decodeScriptError and matchScriptError
Section titled “decodeScriptError and matchScriptError”Two free functions decode a revert against a script’s ABI on the client — typed over the
script’s declared errors. Both take the script or the compiled artifact (anything with an
abi) and either a caught viem error or raw revert hex:
declare function decodeScriptError<const abi extends Abi | readonly unknown[]>( script: { readonly abi: abi }, input: unknown, // a caught (viem) error or 0x-hex revert data): DecodedScriptError<abi> | undefined; // undefined when the input is not revert-shaped
declare function matchScriptError< const abi extends Abi | readonly unknown[], const handlers extends ScriptErrorHandlers<abi, unknown>,>(script: { readonly abi: abi }, error: unknown, handlers: handlers): HandlerResult<handlers>;// HandlerResult (exported): the union of the handlers' return types| Type | Meaning |
|---|---|
DecodedScriptError<abi> |
A name-discriminated union: one arm per ABI error entry ({ name, args, raw } — the declared errors and the two evs built-ins), plus DecodedBuiltinError |
DecodedBuiltinError |
The built-in arms: Panic (code, meaning), Error (reason), unknown (selector — e.g. a callee error bubbled verbatim) and empty |
ErrorArgsOf<e> |
One error entry’s decoded args, as a name-keyed record |
HandlerResult<handlers> |
The union of the handlers’ return types — what matchScriptError returns |
ScriptErrorHandlers<abi, r> |
The matchScriptError handler record: one handler per declared error (receives the typed args and the decoded arm) plus a required _ default for everything else |
matchScriptError is exhaustive over the declared set — adding an error to the script without a
handler is a type error — and rethrows a value that carries no revert data (a network error, a
timeout) instead of routing it to _. The walkthrough is in
errors and debugging.
ir, serializeIr, deserializeIr
Section titled “ir, serializeIr, deserializeIr”compiled.ir is the frozen, JSON-serializable ScriptIr produced at recording — the same
object as script.ir. Two free functions round-trip it:
import { compile, deserializeIr, evscript, serializeIr, t } from '@maxencerb/evs';
const script = evscript({ name: 'one', args: [] }, (s) => { return s.return({ v: s.lit(t.uint256, 1n) });});
const json = serializeIr(script.ir); // stable JSON: sorted keys, optional fields omittedconst ir = deserializeIr(json); // structural shape + version check; deep-frozen result
ir.irVersion; // 1serializeIr(ir) === json; // true — the round trip is byte-stablecompile(script).ir === script.ir; // true — the artifact carries the same IR objectThe bytecode itself is lowered from eliminateDeadCode(script.ir) — the pass compile()
runs on every compile (see How it works).
It is exported (alias dce) so you can inspect exactly what was compiled: same tables, same
site ids, dead statements gone, and a fixpoint — applying it twice changes nothing.
interpret(script.ir, …) applies it too (opt out with { dce: false }), so a dropped revert
guard does not revert under the interpreter either.
import { compile, eliminateDeadCode, evscript, serializeIr, t } from '@maxencerb/evs';
const script = evscript({ name: 'guarded', args: [t.uint256, t.uint256] }, (s, a, b) => { const unused = a.mul(b); // never read — dropped, its overflow check with it void unused; return s.return({ sum: a.add(b) });});
const compiled = compile(script);const lowered = eliminateDeadCode(compiled.ir); // what the bytecode was lowered fromlowered.values === compiled.ir.values; // true — the value table is never renumberedserializeIr(eliminateDeadCode(lowered)) === serializeIr(lowered); // true — idempotentStability notes:
- The format is versioned:
irVersion: 1.deserializeIrrejects any other version, and the compiler emits only version 1. serializeIremits object keys sorted and omitsundefined-valued optional properties, so two structurally equal IRs serialize to the same string.deserializeIrperforms the structural check only and throwsEvsTypeErrornaming the offending JSON path;compileandinterpretre-validate the IR semantically on entry, so externally sourced IR fails loudly rather than miscompiling.
Testing without a node
Section titled “Testing without a node”The IR is directly executable by the exported reference interpreter:
interpret(script, args, chain, opts?) runs a script against a MockChain (a
staticcall({ to, data }) hook, plus an optional call({ to, data, kind, gas?, value? }) oracle for the
s.call/s.simulate verbs) and returns an InterpResult whose returndata and revert
payloads are bit-for-bit what the compiled bytecode produces: it runs eliminateDeadCode(ir)
like compile() unless opts.dce is false. Passing the script (or this artifact) types args
and the decoded values from the literal ABI, exactly like readContract; interpret(ir, …)
takes a bare ScriptIr (e.g. from deserializeIr), untyped. InterpEnvOverrides (the
opts.env extension) models frame-dependent s.env values per call. See
testing scripts for the full workflow.
options
Section titled “options”compiled.options is the frozen, fully resolved Readonly<Required<CompileOptions>> the
artifact was built with — defaults applied: evmVersion: 'cancun', optimize: false,
identity peephole, no-op onDiagnostic. See evscript for the option-by-option
reference and EVM targets for choosing evmVersion.