Skip to content

fix(evm): read operation inputs when they are submitted - #382

Merged
jxom merged 10 commits into
jxom/evm2-wasmfrom
jxom/evm2-review-366
Aug 6, 2026
Merged

fix(evm): read operation inputs when they are submitted#382
jxom merged 10 commits into
jxom/evm2-wasmfrom
jxom/evm2-review-366

Conversation

@jxom

@jxom jxom commented Aug 6, 2026

Copy link
Copy Markdown
Member

Addresses the review findings on #366: operation inputs are read when submitted rather than when they run, envelopes are built from fields a caller can actually supply, caller-held state is copied at the boundary, and the artifact ships each linked crate's license text.

Five claims did not hold and are covered by regression tests instead: self-destruct wipes, bigint chain IDs, and the detached visit order.

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ox Ready Ready Preview Aug 6, 2026 6:14am

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Aug 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/ox@382

commit: 78db978

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5e16458e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evm/Evm.ts Outdated
Comment on lines +667 to +668
const merged = merge(evm['~config'].block, block)
return attempt(evm, () => apply(evm, { ...evm['~config'], block: merged }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Merge partial blocks when their queued setters run

When an asynchronous EVM receives multiple setters without awaiting each one, every merged value is based on the same pre-queue configuration. For example, queuing setBlock({ number: 5n }) followed by setBlock({ timestamp: 10n }) causes the second operation to restore the old block number, even though omitted fields are meant to remain unchanged. Snapshot the supplied partial block at submission, but merge it with the current block inside the queued attempt; setBlockAndExecutionConfig has the same eager-merge problem.

Useful? React with 👍 / 👎.

Comment thread src/evm/Evm.ts Outdated
Comment on lines +394 to +396
const request = {
envelope: envelope(transaction, evm['~chainId']),
signer: transaction.from,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Copy serialized byte envelopes before queueing

When transaction.serialized is a Uint8Array, envelope() calls Bytes.from, which returns that same array rather than copying it. Consequently, a caller mutating the serialized buffer after callTx or transact returns—but before the asynchronous queue runs or while a database read is pending—still changes the submitted transaction despite this new eager request construction. Copy the resulting envelope bytes before retaining them in the queued request.

Useful? React with 👍 / 👎.

Comment thread src/evm/Evm.ts Outdated
Comment on lines +785 to +790
balanceChanges: [...account.balanceChanges],
codeChanges: [...account.codeChanges],
nonceChanges: [...account.nonceChanges],
storageChanges: account.storageChanges.map((slot) => ({
...slot,
changes: [...slot.changes],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deep-copy block access list change entries

For a queued setBal, the new snapshot copies the change arrays but retains each mutable entry object. If the caller subsequently modifies values such as balanceChanges[0].balance, nonceChanges[0].nonce, or storageChanges[0].changes[0].value, encoding later observes the mutation rather than the values submitted to setBal. Clone the individual change entries as well as their containing arrays.

Useful? React with 👍 / 👎.

Comment thread src/evm/Evm.ts Outdated
Comment on lines +1442 to +1444
TxEnvelope.from({
...rest,
chainId: (rest as { chainId?: number }).chainId ?? Number(chainId),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject null transaction chain IDs

When a fields transaction carries chainId: null, which the loose index-signature input type permits, the new nullish fallback silently replaces it with the EVM chain ID. Previously the null reached envelope validation and was rejected, so malformed or incompletely normalized RPC input can now execute on a different chain context instead of failing. Only fall back when the property is strictly undefined.

Useful? React with 👍 / 👎.

}

describe('fields', () => {
test('behavior: an explicit undefined chain id falls back to the EVM default', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Group tests by their exported functions

This new file exercises Evm.callTx, but its cases are grouped under describe('fields') and describe('blob transactions'); similarly, snapshot.test.ts combines several exported functions under describe('queued operations'). Split these into describe('callTx', ...), describe('setBlock', ...), and the other targeted function blocks so failures follow the repository's required test organization.

AGENTS.md reference: AGENTS.md:L92-L92

Useful? React with 👍 / 👎.

Comment thread src/evm/Evm.ts Outdated
Comment on lines +394 to +395
const request = {
envelope: envelope(transaction, evm['~chainId']),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve promise rejection semantics on asynchronous EVMs

For an asynchronous EVM, callTx and transact are typed to return promises, but moving envelope() before attempt() means normal envelope validation failures now throw synchronously instead of rejecting those promises. Code such as Evm.callTx(fork, invalid).catch(...) therefore crashes before obtaining the promised value, whereas the previous queued encoding produced a rejection. Catch eager encoding errors and return a rejected promise when ~driver is present, while retaining synchronous throws for synchronous EVMs.

Useful? React with 👍 / 👎.

Comment thread src/evm/_test/snapshot.test.ts Outdated
const executed = await queued

// Targeted the original address, not the mutated one.
expect(ExecutedTx.result(executed).status).toBe(true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the system-call snapshot test distinguish the target

This assertion does not prove that the original address was used: the original contract completes successfully, but a system call to the mutated, nonexistent 0xff account also completes successfully as an empty-code call. The test therefore passes even if systemCall still reads options.address when the queue drains; make the two targets return observably different output or commit and inspect the original target's storage.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4f7219986

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evm/_test/sinkGuards.test.ts Outdated
})
})

describe('caller-held state', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Group pending-state tests by export

This block combines tests for PendingState.accountInfo and PendingState.insertStorage under a topic-based name, so failures are not organized by the exported function they exercise. Split these cases into describe('accountInfo', ...) and describe('insertStorage', ...) blocks as required by the repository test convention.

AGENTS.md reference: AGENTS.md:L92-L92

Useful? React with 👍 / 👎.

Comment thread src/evm/_test/commitSource.test.ts Outdated
})
})

describe('self-destruct', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Put the self-destruct case under commitSource

This case exercises Evm.commitSource, but the topic-based describe('self-destruct') leaves it outside the file's existing describe('commitSource', ...) group. Move it into that exported-function block so the suite follows the required test organization.

AGENTS.md reference: AGENTS.md:L92-L92

Useful? React with 👍 / 👎.

Comment thread src/evm/Evm.ts Outdated
): Awaitable<boolean, TxResult.TxResult> {
// Encoded once: a retry replays the submitted transaction, not whatever the
// caller's object became while an uncached read was fetched.
const request = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the existing changesets for the new semantics

This changes the observable behavior of public EVM operations by snapshotting caller inputs at submission, but the commit does not update any changeset. Because this branch already has changesets for call execution, transaction lifecycle, configuration, inspection, and block access lists, update the relevant existing entries rather than adding a duplicate so the release records these semantics.

AGENTS.md reference: AGENTS.md:L118-L120

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: feb4864538

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evm/Evm.ts Outdated
Comment on lines +1167 to +1168
if (block['~engine'] !== evm['~engine']) throw new NoBlockStateError()
return evm['~engine'].takeBlockState(block['~id'])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Snapshot the block-state token before queueing

On an asynchronous EVM, this callback runs in a later queue microtask, so JavaScript callers can mutate the newly object-shaped token after takeBlockState returns but before these properties are read. Changing ~id can make the operation reject for a valid accumulator—or target another generation—rather than consuming the token that was submitted; capture ~engine and ~id before calling attempt, or freeze tokens when creating them.

AGENTS.md reference: AGENTS.md:L49-L49

Useful? React with 👍 / 👎.

Comment thread src/evm/Evm.ts Outdated
Comment on lines +1445 to +1447
const { sidecars: _sidecars, ...rest } = fields as typeof fields & {
sidecars?: unknown
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve blob inference when dropping sidecars

When sidecars is the only EIP-4844 discriminator on a loose fields input, removing it before TxEnvelope.from changes the inferred envelope type to EIP-1559. Such an input previously followed the EIP-4844 path and rejected the missing blobVersionedHashes, but now it can execute as an unrelated type-2 transaction; infer the envelope type before discarding the network wrapper, or retain type: 'eip4844' when sidecars selected it.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2b48e64502

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

]),
),
[notice]: attribution(revision, compiled.metadata),
[thirdParty]: thirdPartyLicenses(compiled.metadata, compiled.texts),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate license texts for native EVM2 builds

When OX_EVM2_NATIVE=1 selects the documented host-build path, compile() returns no texts property, but this unconditional call passes compiled.texts to thirdPartyLicenses, which immediately calls texts.get(...). The native build therefore crashes after completing the expensive Rust/WASM compilation instead of producing its outputs; collect the host Cargo-cache license texts in that branch and include them in Compiled.

AGENTS.md reference: AGENTS.md:L138-L138

Useful? React with 👍 / 👎.

Comment thread scripts/wasm/build-evm2.ts Outdated
'mkdir -p /out/licenses',
'for dir in "$CARGO_HOME"/registry/src/*/*/ "$CARGO_HOME"/git/checkouts/*/*/; do' +
' [ -d "$dir" ] || continue;' +
' name=$(basename "$dir");' +

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Associate git checkout licenses with crate keys

For git dependencies, Cargo stores files under git/checkouts/<repository-hash>/<revision>/, so this basename is a revision rather than the ${entry.name}-${entry.version} key used by thirdPartyLicenses. Consequently, license files copied from git checkouts are never matched to their packages; the generated file already reports the six linked ark-* git crates as having no license file even though their checkout contains repository-level licenses. Resolve git checkout texts to the linked package keys before generating the distributable.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52e17ce018

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wasm/evm2/src/database.rs
Comment on lines +198 to +199
Ok(Bytecode::new_raw_checked(bytes.clone())
.unwrap_or_else(|_| Bytecode::new_legacy(bytes)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align bytecode classification across database sources

When state contains code beginning with 0xef01 that is not a 23-byte delegation designator, this fallback now executes it as legacy for a custom or asynchronous Database, while Database.fromMemory still rejects the exact same bytes through assertCode in src/evm/Database.ts:199-206. Replacing an equivalent database implementation can therefore change whether the state executes at all; preserve new_raw_checked rejection or update every database path consistently after recording and approving the evm2 divergence.

AGENTS.md reference: AGENTS.md:L49-L49

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c550851312

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evm/PendingState.ts Outdated
Comment on lines +242 to +250
records: [
...changes.records.filter(
(record) =>
!(
(record.kind === 'storage' || record.kind === 'storageRead') &&
matches(record)
),
),
{ ...slot, kind: 'storage' as const },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve evm2 visit order when replacing storage

When a detached state contains trailing accountRead or storageRead records, replacing an existing slot removes its original record and appends the new write after those reads. StateChange.visit therefore emits an order that evm2's pending-state visitor would not produce, which can change the behavior of order-sensitive persistence or audit sinks; replace the matching record in place or rebuild the canonical evm2 order instead. insertAccount uses the same append pattern.

AGENTS.md reference: AGENTS.md:L49-L49

Useful? React with 👍 / 👎.

Comment thread scripts/wasm/build-evm2.ts Outdated
Comment on lines +360 to +364
// A crate shipping no license file is named anyway, so a gap is visible
// rather than silently absent.
return text
? `${heading}\n\n${text}`
: `${heading}\n\nNo license file ships with this crate; see its repository.`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Supply license text instead of emitting unresolved gaps

When publishing the package, this fallback leaves required attribution unresolved even though the generated file says every linked crate's own license text is reproduced. Fresh evidence in the regenerated artifact shows twelve linked crates still taking this path, including alloy-consensus, alloy-primitives, ruint, and valuable; resolve repository/workspace-level license files or otherwise provide their distributable notices rather than shipping “see its repository.”

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 844d1b59cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

---

Added `ox/evm`, an EVM backed by `alloy-rs/evm2` compiled to WebAssembly, with read-only transaction execution.
Added `ox/evm`, an EVM backed by `alloy-rs/evm2` compiled to WebAssembly, with read-only transaction execution; every operation reads its inputs when it is submitted, so mutating them afterwards cannot change what runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Snapshot pending state before queueing commitSource

With an asynchronous database, this new guarantee is still false for Evm.commitSource: src/evm/Evm.ts:1253-1255 calls PendingState.changes(state) only when the queued callback runs, while PendingState.insertAccount retains the caller's mutable current and original objects. Mutating either object after commitSource returns but before the queue drains silently changes the state that gets applied; capture a deep snapshot at submission as the other state-bearing operations now do.

AGENTS.md reference: AGENTS.md:L49-L49

Useful? React with 👍 / 👎.

Comment thread src/evm/_test/order.test.ts Outdated
}
}

test('visit replays the order a streamed resolution observes', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Group the remaining top-level tests by export

Fresh evidence in the final tree is that this StateChange.visit case remains a top-level test instead of living under describe('visit', ...); src/evm/_test/borrow.test.ts:45 likewise leaves its callTx behavior at the top level. Wrap these cases in blocks named for the exported functions they exercise so the new suites follow the repository's required organization.

AGENTS.md reference: AGENTS.md:L92-L92

Useful? React with 👍 / 👎.

@jxom
jxom merged commit a2b2237 into jxom/evm2-wasm Aug 6, 2026
12 of 15 checks passed
@jxom
jxom deleted the jxom/evm2-review-366 branch August 6, 2026 06:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78db9786e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

expect(Evm.callTx(twice, transaction())).toEqual(expected)
})

test('behavior: a token from another EVM is refused', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the cross-EVM token test under takeBlockState

This cross-EVM case exercises Evm.takeBlockState, but it is nested inside describe('warmPrecompiles'), so failures are attributed to an unrelated export. Move it into a describe('takeBlockState', ...) block to follow the required per-export test organization.

AGENTS.md reference: AGENTS.md:L87-L92

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant