Skip to content

DAO-2226 When 1 feeTier from available ones doesn't have liquidity "auto" fails#2081

Open
TravellerOnTheRun wants to merge 6 commits intomainfrom
dao-2226
Open

DAO-2226 When 1 feeTier from available ones doesn't have liquidity "auto" fails#2081
TravellerOnTheRun wants to merge 6 commits intomainfrom
dao-2226

Conversation

@TravellerOnTheRun
Copy link
Copy Markdown
Contributor

Why this exists

The swap stack talks to Rootstock through normal JSON-RPC eth_call. When we ask Uniswap V3 for quotes across several fee tiers, we do not fire one HTTP request per contract call if we can avoid it: we batch those reads through Multicall3, so a single eth_call runs many small view calls inside one on-chain aggregate. That is the usual pattern on public RPCs and keeps latency and load down.

The problem: that outer eth_call is still one EVM execution with a single gas budget (the block gas limit the node applies to the call). A batch that is fine on a forgiving public endpoint can fail on Anvil or other strict setups with an Out of gas style error for the whole multicall. From the app’s perspective that looks like the entire quote path failed, even though each inner quoter read might succeed if sent separately. So fork-based development and CI can disagree with “it works on mainnet RPC” without any change to pool math or business logic.

What we changed conceptually

  1. Graceful degradation on outer failures — If the batched multicall fails in a way that matches this class of RPC / envelope errors, we retry once with a smaller multicall shape (effectively one inner call per batch chunk) so the same logic still runs, just with more round trips when the node is tight on gas.

  2. Same idea for token decimals — Decimals are also read on-chain; batching and the same retry path keeps behavior consistent and avoids redundant calls (duplicate addresses are collapsed).

  3. Align fork and CI with real gas headroom — Local fork and the workflow that starts Anvil use a higher block gas limit so developers and CI are not randomly blocked by default limits that do not match how public RPCs behave. This does not remove the need for the retry; it reduces noise when comparing fork vs production.

  4. Documentation — There is a short, onboarding-oriented explanation of Multicall3, why one fat eth_call can fail as a unit, and how to inspect RPC traffic in the browser when debugging. No extra tooling is required for that.

Together, this is about robustness and parity: quotes and decimals should behave predictably whether you are on a public RPC, a local fork, or CI, without giving up batching when the environment allows it.

When viem multicall throws (e.g. EVM OutOfGas on strict eth_call envelopes),
retry once with batchSize 1 so each Quoter read uses a smaller aggregate3 chunk.
Route decimals reads through multicallWithGasEnvelopeRetry, dedupe addresses,
skip RPC for empty input, and align fork test setup with one batch read.
Align local fork with heavier Multicall3 eth_call envelopes so batch quotes
do not fail the whole call with OutOfGas under default limits.
Use the same high block gas limit as local fork:anvil so CI eth_call
multicall batches behave like developer machines.
Document inner vs outer multicall failures, why Anvil can differ from public
RPC, the fork:anvil and CI gas flags, app-level batch retry, and how to
inspect eth_call traffic in the browser.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 13, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA fea0235.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@TravellerOnTheRun TravellerOnTheRun self-assigned this Apr 13, 2026
@TravellerOnTheRun TravellerOnTheRun requested a review from a team April 13, 2026 09:03
@TravellerOnTheRun TravellerOnTheRun added the dao PRs made by DAO team label Apr 13, 2026
viem maps a rejected aggregate3 eth_call to one failure row per contract
instead of throwing; detect shared envelope-style errors and retry with
batchSize 1. Consolidate fingerprint substrings; use the wrapper in fork
multihop test helper so expectations match production.
@TravellerOnTheRun TravellerOnTheRun changed the title DAO-226 When 1 feeTier from available ones doesn't have liquidity "auto" fails DAO-2226 When 1 feeTier from available ones doesn't have liquidity "auto" fails Apr 13, 2026
export function isLikelyOuterMulticallRpcFailure(err: unknown): boolean {
const fp = errorFingerprint(err)
return fingerprintMatchesChunkEnvelope(fp) || fp.includes('evm error')
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Using a broad evm error match for retry could mask legitimate reverts; consider narrowing to known RPC/gas envelope errors (or at least logging before retry).

@Ken-li-iov Ken-li-iov self-requested a review April 14, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dao PRs made by DAO team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants