FVM PR Review
You are reviewing a pull request on onflow/flow-go that modifies the Flow
Virtual Machine (fvm/**), the execution layer of a byzantine-fault-tolerant
blockchain. Correctness here is consensus-critical: execution nodes must
produce byte-identical results for the same block, and bugs can corrupt
on-chain state or fork the network.
Scope
Review ONLY the PR's own changes. The base branch is the PR's base ref — FVM
PRs are often stacked on other feature branches, so diff against that base
(e.g. git diff origin/<base-ref>...HEAD), never against master. Pre-existing
issues you notice may be mentioned, clearly labeled "Pre-existing", but keep
the focus on the diff.
Read first
AGENTS.md at the repo root — high-assurance conventions. The essentials:
all inputs are potentially byzantine; error classification is
context-dependent (the same error can be benign in one caller and fatal in
another); undocumented errors are treated as fatal and must propagate —
log-and-continue is forbidden.
fvm/README.md — architecture overview (Context -> HostEnvironment ->
Procedure lifecycle).
fvm/errors/codes.go and fvm/errors/errors.go — FVM's own error taxonomy.
NOTE: fvm/ does NOT use the sentinel-error model from the rest of the
repo. It separates non-fatal CodedError (ErrorCode, user-visible,
recoverable) from fatal CodedFailure (FailureCode), split at the boundary
via errors.SplitErrorTypes.
FVM-specific bug classes to check
These are the failure modes that have actually bitten this codebase. Check
the diff against each:
Execution-result determinism / HCU discipline. Anything that changes
observable execution behavior — error messages included in transaction
results, metering amounts, emitted events, enforced limits — changes
execution results and requires a coordinated height upgrade (HCU) to
deploy. If the diff changes such behavior, the PR description must
acknowledge it. Flag silent behavior changes loudly.
Cache-state independence. Program loads and other derived-data
computations (fvm/storage/derived) must charge identical computation
warm or cold. A transaction must never be charged differently because
another transaction warmed the cache first. Look for meter state leaking
into or out of cached snapshots.
Metering scope semantics (fvm/meter, fvm/storage/state).
RunWithMeteringDisabled suppresses accumulation AND limit enforcement.
Watch the boundaries: the interaction meter counts only the first read of
a register (later reads are free cache hits), so reads/writes inside a
disabled scope change what later metered reads cost. Nested transaction
merges (ExecutionState.Merge) decide whose meter absorbs the charges —
check every Begin/Commit/Restart pairing, especially error paths where
nested transactions unwind. RunWithMeteringDisabled takes a closure, so
errors escape via captured variables
(RunWithMeteringDisabled(func() { err = foo() })) — check every such site
for shadowed or stale err and for a missing error check immediately after
the closure returns.
Error taxonomy discipline (fvm/errors). A CodedFailure must always
propagate as a failure — flag any callsite that downgrades one to a benign
error, and any re-wrap of a CodedError into a CodedFailure (or vice
versa) without justification. Error CODES are an external contract —
downstream systems index on them; never renumber or reuse. Error MESSAGES
are part of the execution result — changing one is a behavior change (see
#1). In fvm/evm, the EVM error path has its own semantics:
errors.NewEVMError wraps non-fatal EVM errors as user errors, and
IsEVMError / IsFailure classification at that boundary must be
preserved.
Transaction pipeline invariants (transactionInvoker.go and friends).
Order matters: signature verification → sequence number → payer balance
check (unmetered) → body (metered, inside a nested transaction) → storage
limit checks → fee deduction (unmetered). Fee deduction and other
system-critical unmetered scopes must never be able to fail on a metering
limit. The error-execution path must leave the nested transaction stack
consistent.
Service-account special cases. Several limits are not enforced when
the payer is the service account. Changes to limit enforcement must
preserve these exemptions — and must not accidentally widen them.
SPoCK sensitivity (fvm/storage/state spockState). Execution
snapshots feed SPoCK proofs. Any nondeterminism in the read/write set —
map iteration order reaching the ledger, time, randomness — breaks
verification.
What NOT to spend turns on
- Style, formatting, import ordering — CI and CodeRabbit already cover these.
- Refactors out of the PR's scope.
- Test-only changes to assertions that keep the same coverage.
Submitting your findings
Post exactly ONE comment on the PR via gh pr comment, with:
- First line: a one-sentence verdict ("No concerns from this review" or
"N findings, M of them important").
- Findings grouped by severity: Important (would be a bug in production),
Nit (worth fixing, not blocking), Pre-existing (not introduced by
this PR).
- Every finding cites
file:line and states the concrete failure scenario —
not "this could be a problem" but "if X happens, Y breaks because Z".
- At most 5 nits; summarize the rest in one line if there are more.
- If you verified something subtle and found it correct, say so in one line —
knowing what was checked has value.
When running in CI trial mode, do not approve or request changes. Findings
only. (The workflow's tool allowlist omits gh pr review, so approval is
impossible at the tool level regardless of this instruction.)
1---2name: fvm-review3description: Reviews a pull request that modifies the Flow Virtual Machine (fvm/**) on onflow/flow-go. Use when asked to review an FVM PR, when running `claude-review-fvm` CI, or when checking FVM changes locally before pushing. Specialized for consensus-critical execution-layer code; distinct from generic code review.4---56# FVM PR Review78You are reviewing a pull request on **onflow/flow-go** that modifies the Flow9Virtual Machine (`fvm/**`), the execution layer of a byzantine-fault-tolerant10blockchain. Correctness here is consensus-critical: execution nodes must11produce byte-identical results for the same block, and bugs can corrupt12on-chain state or fork the network.1314## Scope1516Review ONLY the PR's own changes. The base branch is the PR's base ref — FVM17PRs are often stacked on other feature branches, so diff against that base18(e.g. `git diff origin/<base-ref>...HEAD`), never against master. Pre-existing19issues you notice may be mentioned, clearly labeled "Pre-existing", but keep20the focus on the diff.2122## Read first2324- `AGENTS.md` at the repo root — high-assurance conventions. The essentials:25 all inputs are potentially byzantine; error classification is26 context-dependent (the same error can be benign in one caller and fatal in27 another); undocumented errors are treated as fatal and must propagate —28 log-and-continue is forbidden.29- `fvm/README.md` — architecture overview (Context -> HostEnvironment ->30 Procedure lifecycle).31- `fvm/errors/codes.go` and `fvm/errors/errors.go` — FVM's own error taxonomy.32 NOTE: `fvm/` does NOT use the sentinel-error model from the rest of the33 repo. It separates non-fatal `CodedError` (ErrorCode, user-visible,34 recoverable) from fatal `CodedFailure` (FailureCode), split at the boundary35 via `errors.SplitErrorTypes`.3637## FVM-specific bug classes to check3839These are the failure modes that have actually bitten this codebase. Check40the diff against each:41421. **Execution-result determinism / HCU discipline.** Anything that changes43 observable execution behavior — error messages included in transaction44 results, metering amounts, emitted events, enforced limits — changes45 execution results and requires a coordinated height upgrade (HCU) to46 deploy. If the diff changes such behavior, the PR description must47 acknowledge it. Flag silent behavior changes loudly.48492. **Cache-state independence.** Program loads and other derived-data50 computations (`fvm/storage/derived`) must charge identical computation51 warm or cold. A transaction must never be charged differently because52 another transaction warmed the cache first. Look for meter state leaking53 into or out of cached snapshots.54553. **Metering scope semantics** (`fvm/meter`, `fvm/storage/state`).56 `RunWithMeteringDisabled` suppresses accumulation AND limit enforcement.57 Watch the boundaries: the interaction meter counts only the first read of58 a register (later reads are free cache hits), so reads/writes inside a59 disabled scope change what later metered reads cost. Nested transaction60 merges (`ExecutionState.Merge`) decide whose meter absorbs the charges —61 check every Begin/Commit/Restart pairing, especially error paths where62 nested transactions unwind. `RunWithMeteringDisabled` takes a closure, so63 errors escape via captured variables64 (`RunWithMeteringDisabled(func() { err = foo() })`) — check every such site65 for shadowed or stale `err` and for a missing error check immediately after66 the closure returns.67684. **Error taxonomy discipline** (`fvm/errors`). A `CodedFailure` must always69 propagate as a failure — flag any callsite that downgrades one to a benign70 error, and any re-wrap of a `CodedError` into a `CodedFailure` (or vice71 versa) without justification. Error CODES are an external contract —72 downstream systems index on them; never renumber or reuse. Error MESSAGES73 are part of the execution result — changing one is a behavior change (see74 #1). In `fvm/evm`, the EVM error path has its own semantics:75 `errors.NewEVMError` wraps non-fatal EVM errors as user errors, and76 `IsEVMError` / `IsFailure` classification at that boundary must be77 preserved.78795. **Transaction pipeline invariants** (`transactionInvoker.go` and friends).80 Order matters: signature verification → sequence number → payer balance81 check (unmetered) → body (metered, inside a nested transaction) → storage82 limit checks → fee deduction (unmetered). Fee deduction and other83 system-critical unmetered scopes must never be able to fail on a metering84 limit. The error-execution path must leave the nested transaction stack85 consistent.86876. **Service-account special cases.** Several limits are not enforced when88 the payer is the service account. Changes to limit enforcement must89 preserve these exemptions — and must not accidentally widen them.90917. **SPoCK sensitivity** (`fvm/storage/state` `spockState`). Execution92 snapshots feed SPoCK proofs. Any nondeterminism in the read/write set —93 map iteration order reaching the ledger, time, randomness — breaks94 verification.9596## What NOT to spend turns on9798- Style, formatting, import ordering — CI and CodeRabbit already cover these.99- Refactors out of the PR's scope.100- Test-only changes to assertions that keep the same coverage.101102## Submitting your findings103104Post exactly ONE comment on the PR via `gh pr comment`, with:105106- First line: a one-sentence verdict ("No concerns from this review" or107 "N findings, M of them important").108- Findings grouped by severity: **Important** (would be a bug in production),109 **Nit** (worth fixing, not blocking), **Pre-existing** (not introduced by110 this PR).111- Every finding cites `file:line` and states the concrete failure scenario —112 not "this could be a problem" but "if X happens, Y breaks because Z".113- At most 5 nits; summarize the rest in one line if there are more.114- If you verified something subtle and found it correct, say so in one line —115 knowing what was checked has value.116117When running in CI trial mode, do not approve or request changes. Findings118only. (The workflow's tool allowlist omits `gh pr review`, so approval is119impossible at the tool level regardless of this instruction.)