Skip to content

🐛 Skip overhead cache when account contract is not yet deployed - #79

Merged
florian-bellotti merged 1 commit into
mainfrom
fix/braavos-overhead-skip-cache-on-contract-not-found
May 12, 2026
Merged

florian-bellotti merged 1 commit into
mainfrom
fix/braavos-overhead-skip-cache-on-contract-not-found

Conversation

@florian-bellotti

Copy link
Copy Markdown
Contributor

Summary

ValidationGasOverhead::fetch used to convert every error from client.call(get_signers) into Ok(Self::none()), and resolve_gas_overhead cached every result indefinitely. As a side effect, any address queried while its contract did not yet exist — typically during a deploy_and_invoke flow that deploys the wallet in the same transaction it is estimating — got permanently marked as "no overhead" in the cache. Once the wallet was actually deployed and the user did follow-up sponsored invokes, the cached none() was returned instead of the Braavos overhead, the resource bound on l2_gas was set ~5x too low, and Braavos passkey transactions reverted with Insufficient max L2Gas: max amount: 9422157, actual used: 48940800.

This PR distinguishes error variants in fetch:

  • successful response → cache (Braavos or none, based on shape)
  • contract-level execution error (account exists but does not expose get_signers, e.g. Argent / OZ / custom accounts) → cache as none()
  • ContractNotFound (wallet not yet deployed — the case behind this bug) → return none() for the current request but skip the cache so the next request retries the detection once the wallet is on-chain
  • any other transient RPC error (timeout, rate-limit, internal, ...) → same as ContractNotFound: do not cache

Also extracts the response-shape classification into a pure function (from_get_signers_response) and adds unit tests covering the on-chain shapes observed for a classic Braavos (4 felts) and a Braavos with a passkey/secp256r1 signer (5 felts).

Diagnosis trail (for reference)

Confirmed on tx 0x4b7a1cb5215c43c9f60735b1361b9ad2e43f2f446a968e7de152f2edf82baee (mainnet, block 9680273):

  • resource_bounds.l2_gas.max_amount = 0x8fc54d = 9 422 157 on-chain
  • Back-computing through the multipliers (/1.5 estimate, /1.15 provider) yields a raw simulator l2_gas_consumed ≈ 5.46M — i.e. no Braavos overhead was added to the recomputed fee
  • If the 46.64M Braavos overhead had been applied, the resource bound would have been ~90M (more than enough for the ~48.9M actually consumed)
  • Service logs around the wallet's deployment confirm get_signers returned ContractNotFound during paymaster_buildTransaction and paymaster_executeTransaction of the deploy_and_invoke, which got swallowed into Ok(Self::none()) and cached

Scope

This PR fixes the cache-poisoning dimension of the bug, which is the one observed on the reported transaction (the wallet was queried before deployment, the cached none() then persisted for the subsequent sponsored invoke).

It does not address the related case where the failing transaction is itself the deploy_and_invoke of a Braavos passkey wallet (get_signers is fundamentally unreachable at simulation time because the wallet is being deployed in the same transaction). That requires reading the account type from the deployment.class_hash / sigdata, and will be handled in a follow-up.

Test plan

  • cargo build --workspace passes
  • cargo clippy -p paymaster-execution passes
  • cargo test -p paymaster-execution --lib — all 36 existing tests pass + 3 new ones cover the response-shape classification
  • Manual confirmation against a Braavos passkey sponsored invoke on mainnet after deploy (requires running the patched service against the reported failure scenario)

@vercel

vercel Bot commented May 11, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
paymaster Ready Ready Preview, Comment May 11, 2026 7:52pm

Request Review

@florian-bellotti
florian-bellotti force-pushed the fix/braavos-overhead-skip-cache-on-contract-not-found branch from 2a76676 to f4794cc Compare May 11, 2026 19:50
`ValidationGasOverhead::fetch` used to convert every error from
`client.call(get_signers)` into `Ok(Self::none())`, and the caller
(`resolve_gas_overhead`) cached the result indefinitely. As a side
effect, any address queried while its contract did not yet exist —
typically during a `deploy_and_invoke` flow that deploys the wallet in
the same transaction it is estimating — got permanently marked as
"no overhead". Once the wallet was deployed and the user did follow-up
sponsored invokes, the cached `none()` was returned instead of the
Braavos overhead, the resource bound on `l2_gas` was set ~5x too low,
and Braavos passkey transactions reverted with
`Insufficient max L2Gas`.

Distinguish error variants in `fetch`:

- successful response → cache (Braavos or none, based on shape)
- contract-level execution error (account exists but does not expose
  `get_signers`, e.g. Argent/OZ) → cache as `none()`
- `ContractNotFound` (wallet not yet deployed, the case behind this
  bug) → return `none()` for the current request but skip the cache so
  the next request retries the detection
- any other transient RPC error (timeout, rate-limit, ...) → same as
  `ContractNotFound`: do not cache

Also extract the response-shape classification into a pure function
(`from_get_signers_response`) and add unit tests covering the on-chain
shapes observed for a classic Braavos (4 felts) and a Braavos with a
passkey/secp256r1 signer (5 felts).
@florian-bellotti
florian-bellotti force-pushed the fix/braavos-overhead-skip-cache-on-contract-not-found branch from f4794cc to d25c198 Compare May 11, 2026 19:52
@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.47059% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/paymaster-execution/src/starknet/mod.rs 0.00% 5 Missing ⚠️
.../paymaster-execution/src/execution/fee/overhead.rs 89.65% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@florian-bellotti
florian-bellotti merged commit b97a12b into main May 12, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — d25c198a Deployed May 11, 2026 by vercel[bot]
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.

2 participants