Skip to content

fix[venom]: guard copy-forwarding against deep-callee gas bombs#4974

Open
harkal wants to merge 6 commits into
vyperlang:masterfrom
harkal:fix/venom/copy-forwarding-deep-callee
Open

fix[venom]: guard copy-forwarding against deep-callee gas bombs#4974
harkal wants to merge 6 commits into
vyperlang:masterfrom
harkal:fix/venom/copy-forwarding-deep-callee

Conversation

@harkal

@harkal harkal commented May 17, 2026

Copy link
Copy Markdown
Collaborator

What I did

Hardened CopyForwardingPolicy against quadratic memory-expansion gas bombs when the mcopy source operand can't be resolved to an alloca (typically a function param). On master alone, no contract in the snekmate corpus currently triggers this shape, but it materializes on branches whose inlining decisions are less aggressive (observed at ~135M gas per call on snekmate ERC1155 batch revert paths).

How I did it

Two structural changes in vyper/venom/passes/copy_forwarding.py:

  • _callee_reserved_intervals now walks transitive callees in its pre-allocator fallback. The previous local-only walk silently missed deeper allocas, which is a precision bug independent of the bomb scenario.
  • should_block_forwarding now has an explicit branch for unresolved sources. It estimates the per-invoke memory-expansion penalty using the copy size as a proxy and bails when the penalty exceeds an absolute 1M-gas threshold. Small-frame forwardings still win (hundreds of gas); ABI-encode-buffer-scale frames (megabytes -> millions of gas) are blocked.

An alternative defer all unresolved-source forwarding decisions to the post-inline per-function pass (was evaluated and rejected). It eliminates the magic number but loses ~67 forwarding wins across the snekmate corpus, because non-inlined functions with param sources keep src=None in both passes and the post-inline pass can't tell "never-inlined" apart from "will-be-inlined."

How to verify it

Commit message

`CopyForwardingPolicy.should_block_forwarding` previously returned False
unconditionally when the mcopy source couldn't be resolved to an alloca
(typically a function `param`). That's a soundness gap: if the enclosing
function gets inlined later, the param resolves to a caller alloca whose
liveness now extends across the deep invoke. The conflict graph can
force that caller alloca above the callee's transitive pinned set, and
on snekmate ERC1155 batch revert paths under inlining shapes that expose
this pattern, the resulting quadratic memory-expansion gas is ~135M per
call.

Two changes plug the gap. `_callee_reserved_intervals` now walks
transitive callees in its pre-allocator fallback -- the old local-only
walk silently missed deeper allocas, a precision bug in its own right.
And `should_block_forwarding` now estimates the per-invoke memory-
expansion penalty even when the source is unresolved, using the copy
size as a proxy, and bails when the penalty exceeds an absolute 1M-gas
threshold. Small-frame forwardings still win (hundreds of gas); ABI-
encode-buffer-scale frames (megabytes -> millions of gas) are caught.

A defer-to-post-inline-pass alternative was evaluated and rejected. It
removes the magic number but loses ~67 forwarding wins across the
snekmate corpus: non-inlined functions with `param` sources have
src=None in both passes, so the post-inline pass can't tell "never-
inlined" apart from "will-be-inlined" and conservatively keeps the
mcopy.

Description for the changelog

Cute Animal Picture

`CopyForwardingPolicy.should_block_forwarding` previously returned False
unconditionally when the mcopy source couldn't be resolved to an alloca
(typically a function `param`). That's a soundness gap: if the function
gets inlined later, the param resolves to a caller alloca whose
liveness now extends across the deep invoke, and the conflict graph
can force it to a high address. The quadratic memory-expansion gas
this produces can dwarf the forwarding win by many orders of magnitude
(observed: ~135M gas on snekmate ERC1155 batch revert paths under
branches whose inlining decisions expose this shape).

Two changes:

- `_callee_reserved_intervals` now walks transitive callees in its
  pre-allocator fallback. The old local-only walk silently missed
  deeper allocas — a precision bug that mattered regardless of the
  unresolved-source branch.

- `should_block_forwarding` now estimates the per-invoke memory-
  expansion penalty even when the source is unresolved, using the
  copy size as a proxy. Bail only when the penalty exceeds an
  absolute 1M-gas threshold, so small-frame forwardings still win
  (hundreds of gas) but ABI-encode-buffer-scale frames (megabytes →
  millions of gas) are caught.

No change in measured gas on the snekmate corpus under master's
inlining patterns — the bomb shape is not currently triggered there.
The fix is precautionary on master and load-bearing on any branch
whose inlining is less aggressive (e.g. feat/venom/dyn_alloca).

A `defer-to-post-inline-pass` alternative was evaluated and rejected:
it eliminates the magic number but loses ~67 forwarding wins across
snekmate because non-inlined functions with `param` sources have
`src=None` in both passes, so the post-inline pass can't tell
"never-inlined" apart from "will-be-inlined" and conservatively keeps
the mcopy.
@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown

Gas Changes

No changes detected.

Summary

  • Total tests measured: 803
  • Changed: 0
  • Regressions (gas up): 0
  • Improvements (gas down): 0
  • New tests: 0
  • Deleted tests: 0
  • Newly failing: 0
  • Newly passing: 0

@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown

📊 Bytecode Size Changes (venom)

No changes detected.

Full bytecode sizes

Contract legacy-O2 legacy-Os -O2 -O3 -Os
curvefi/amm/stableswap/meta_implementation/meta_implementation_v_700.vy 23610 22805 20801 19770 19330
curvefi/legacy/CurveStableSwapMetaNG.vy 24952 23578 20644 20025 19329
curvefi/amm/stableswap/implementation/implementation_v_700.vy 24962 23769 20467 19596 19048
curvefi/legacy/CurveStableSwapNG.vy 24473 23298 19860 19016 18497
curvefi/amm/tricryptoswap/implementation/implementation_v_200.vy 20724 19959 18018 17516 16959
yearnfi/VaultV3.vy 19972 19063 17017 15131 14477
curvefi/amm/twocryptoswap/implementation/implementation_v_210.vy 17634 16894 15685 15132 14648
curvefi/legacy/CurveCryptoSwap2.vy 18947 18382 15622 15068 14699
yearnfi/VaultV2.vy 16676 15763 13863 13405 12574
curvefi/amm/stableswap/factory/factory_v_100.vy 14558 13978 13250 11856 11829
curvefi/amm/stableswap/views/views_v_120.vy 12784 12368 10291 9734 9853
curvefi/gauge/child_gauge/implementation/implementation_v_110.vy 12338 11561 10270 9775 9182
curvefi/gauge/child_gauge/implementation/implementation_v_100.vy 12017 11249 9998 9502 8920
curvefi/amm/tricryptoswap/math/math_v_200.vy 11189 11126 9506 8143 8410
curvefi/legacy/CurveCryptoMathOptimized3.vy 11188 11125 9505 8143 8410
curvefi/gauge/child_gauge/implementation/implementation_v_020.vy 10665 9947 9013 8565 8028
curvefi/helpers/router/router_v_110.vy 6717 6717 7003 6376 6607
curvefi/amm/tricryptoswap/views/views_v_200.vy 7821 7776 6717 6465 6534
curvefi/registries/metaregistry/metaregistry_v_110.vy 7590 6732 6657 5894 5733
curvefi/helpers/stable_swap_meta_zap/stable_swap_meta_zap_v_100.vy 7302 7067 6540 6173 6275
curvefi/amm/twocryptoswap/views/views_v_200.vy 6991 6946 6274 6028 6091
curvefi/registries/metaregistry/registry_handlers/stableswap/handler_v_110.vy 6633 6259 5859 5094 5562
curvefi/amm/tricryptoswap/factory/factory_v_200.vy 5246 5021 5816 4849 4953
curvefi/amm/twocryptoswap/factory/factory_v_200.vy 5540 5252 5816 4579 4740
curvefi/amm/twocryptoswap/math/math_v_210.vy 6800 6800 5581 5107 5112
curvefi/gauge/child_gauge/factory/factory_v_201.vy 4844 4547 4266 4014 3780
yearnfi/VaultFactory.vy 3765 3617 3901 2453 2913
curvefi/registries/metaregistry/registry_handlers/tricryptoswap/handler_v_110.vy 4241 3939 3838 3547 3547
curvefi/registries/metaregistry/registry_handlers/twocryptoswap/handler_v_110.vy 4186 3884 3777 3417 3433
curvefi/gauge/child_gauge/factory/factory_v_100.vy 4183 3914 3694 3428 3213
curvefi/helpers/rate_provider/rate_provider_v_101.vy 3260 3260 2807 2465 2478
curvefi/registries/address_provider/address_provider_v_201.vy 2973 2782 2719 2575 2430
curvefi/amm/stableswap/math/math_v_100.vy 3067 3046 2598 2403 2423
curvefi/helpers/rate_provider/rate_provider_v_100.vy 2847 2841 2373 2066 2074
curvefi/helpers/deposit_and_stake_zap/deposit_and_stake_zap_v_100.vy 2322 2316 2123 1889 1921
curvefi/governance/relayer/taiko/relayer_v_001.vy 2068 2064 1647 1532 1565
curvefi/governance/relayer/polygon_cdk/relayer_v_101.vy 1556 1523 1454 1362 1363
curvefi/governance/relayer/arb_orbit/relayer_v_101.vy 1266 1262 1147 1081 1112
curvefi/governance/relayer/op_stack/relayer_v_101.vy 1186 1182 1086 1023 1051
curvefi/governance/relayer/not_rollup/relayer_v_100.vy 1168 1153 1075 1018 1030
curvefi/governance/vault/vault_v_100.vy 964 941 885 866 864
curvefi/governance/relayer/relayer_v_100.vy 496 496 467 462 467
curvefi/governance/agent/agent_v_100.vy 541 541 443 415 419
curvefi/governance/agent/agent_v_101.vy 541 541 443 415 419

@codecov

codecov Bot commented May 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.94%. Comparing base (b4145b7) to head (29196c9).

Files with missing lines Patch % Lines
vyper/venom/passes/copy_forwarding.py 94.73% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4974      +/-   ##
==========================================
+ Coverage   92.92%   92.94%   +0.01%     
==========================================
  Files         188      188              
  Lines       27820    27847      +27     
  Branches     4828     4836       +8     
==========================================
+ Hits        25853    25882      +29     
+ Misses       1316     1315       -1     
+ Partials      651      650       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@harkal harkal marked this pull request as ready for review May 18, 2026 17:07

@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: 39a25bde03

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread vyper/venom/passes/copy_forwarding.py

@HodanPlodky HodanPlodky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just two small things otherwise lgtm

Comment thread vyper/venom/passes/copy_forwarding.py Outdated
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.

3 participants