Skip to content

fix(healthCheck): eliminate false failures + refresh target state (68/71 networks red -> green) - #2120

Merged
0xDEnYO merged 16 commits into
mainfrom
fix/healthcheck-false-failures
Jul 28, 2026
Merged

fix(healthCheck): eliminate false failures + refresh target state (68/71 networks red -> green)#2120
0xDEnYO merged 16 commits into
mainfrom
fix/healthcheck-false-failures

Conversation

@0xDEnYO

@0xDEnYO 0xDEnYO commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Which Linear task belongs to this PR?

EXSC-680

Note

BE confirmation (2026-07-28): Backend has switched over to LiFiIntentEscrowFacetV2. The V2-as-core + target-state couple in this PR is unblocked for merge.

Why did I implement it this way?

The nightly Diamond Health Check (all networks) reported 68 of 71 networks failing (run 30192494683). Bucketing every per-network reason showed it was 6 root causes, and most of the volume was the check disagreeing with config rather than real on-chain drift. This PR fixes the parts that are ours to fix in code; the genuine drift is called out below and left alone.

Root cause Networks Fixed here
LiFiIntentEscrowFacet in coreFacets but deployed on 13 chains 58 ✅ grandfathered
Composer pairs reported "stale, not in config" — but they are in config 22 ✅ real bug
FeeCollector owner mismatch 5 ✅ check removed (deprecated)
ReceiverAcrossV3.executor() mismatch 3 ✅ entry removed (deprecated)
ERC20Proxy owner mismatch 6 ❌ real drift — see #2088
Individually missing facets 8 ❌ real, needs deploys

54 of the 68 go green from this PR alone.

1. whitelist-integrity was resolving periphery addresses by the wrong key

getExpectedPairs looked periphery contracts up as deployedContracts[peripheryContract.name], ignoring the address field that config/whitelist.json carries (and that IWhitelistConfig declares as required). Two consequences:

  • Composer is whitelisted but never deployed by this repo, so it is absent from every deployments/*.json. contractAddr came back undefined, the entry was silently skipped, and all of its on-chain pairs were then reported as stale.
  • A name-keyed lookup returns one address, but a network can list several distinct Composer addresses — e.g. PERIPHERY.arbitrum has three, which is exactly the "3 stale pairs" in the log.

Config is the right source of truth for a check that asks "does the diamond's whitelist match config". I also made the two failure modes visible instead of silent: a warning when an entry resolves to no address at all, and a warning when a config address disagrees with a contract we did deploy (that means the whitelist entry is stale and diamondSyncWhitelist would whitelist the wrong address).

2. Making a facet core "going forward" — CORE_FACET_EXEMPTIONS

LiFiIntentEscrowFacet was added to coreFacets in #1859, which makes the check demand it on all networks, but it was only deployed to the chains in #1997. Rather than drop it from coreFacets, this adds a narrow, per-network exemption table:

  • the facet stays in coreFacets, so any newly onboarded network is absent from the table and therefore enforced by default — the safe direction, and what "core going forward" should mean;
  • the listed pre-existing networks are exempt until the facet is backfilled.

Deliberately narrower than the existing HEALTH_CHECK_EXCLUSIONS, which disables a whole invariant on a network and would have cost coverage for every other core facet. This drops one facet from the expected set and leaves core-facets-deployed / facets-registered otherwise fully enforced.

The networks list is a shrinking to-do, not a steady state: delete a network the moment the facet lands there, and delete the entry when the list empties. Four tests guard it — the facet must really be in coreFacets, every network must exist in config/networks.json, the reason must be non-empty, no duplicates — so a stale exemption fails CI instead of quietly hiding a gap. Exemptions apply to the health check only; deployCoreFacets.sh still reads coreFacets, so deploying it everywhere stays a one-command job.

3. Deprecated checks

feecollector-owner removed (FeeCollector is deprecated; its owner is no longer maintained against config.feeCollectorOwner, which the deploy scripts still read). ReceiverAcrossV3 dropped from RECEIVER_EXECUTOR_GETTERS — superseded by V4, which stays checked along with Chainflip, OIF and StargateV2. The now-unused feeCollectorOwner context plumbing is removed rather than left dead.

4. The log was overstating severity

executeInvariant printed its banner as `[${invariant.severity}] ${name} — ${description}`. That banner runs once per invariant per network, so ~1.3k passing checks rendered as [error] diamond-deployed — LiFiDiamond is deployed, each immediately followed by [success] LiFiDiamond deployed. Grep-indistinguishable from real failures, and most of why the run reads as catastrophic. Now name — description (severity: error).

Verification

Run against production RPCs from this branch:

  • katana (previously failed on stale pairs) → ✔ Pair Array (getAllContractSelectorPairs) is synced. (20 pairs), ✔ Deployment checks passed
  • flow (previously failed on the missing facet) → exemption printed with its reason, ✔ Deployment checks passed, exit 0
  • xdc (previously failed on FeeCollector owner) → check no longer runs

bun test script/ → 570 pass / 0 fail. tsc --noEmit, eslint and prettier clean.

Deliberately not in scope

  • ERC20Proxy owner on bob, etherlink, jovay, morph, nibiru, swellchain — real drift, needs the two-step ownership transfer via governance. feat(scripts): ERC20Proxy ownership remediation toolkit (EXSC-660) #2088 looks like it covers exactly this.
  • Missing facets on blast, boba, bsc, mainnet, metis, plume, sei, zksync — real gaps, need deploys.
  • Could not enumerate ERC20Proxy authorized callers (RPC log range limit?) — one unchunked eth_getLogs silently skipping that sub-check on 45 networks. Real coverage loss, pre-existing, worth its own PR.
  • Tron 429s — Tron needs its own throttle below the shared concurrency of 8. Pre-existing.

Checklist before requesting a review

  • I have performed a self-review of my code
  • This pull request is as small as possible and only tackles one problem
  • I have added tests that cover the functionality / test the bug
  • For new facets: n/a
  • I have updated any required documentation

🤖 Generated with Claude Code

The scheduled Diamond Health Check reported 68/71 networks failing. Most of
that was the check disagreeing with config, not real on-chain drift.

- whitelist-integrity: resolve periphery addresses from the `address` field in
  config/whitelist.json instead of by name from the deployments file. Contracts
  we whitelist but do not deploy (Composer) were absent from deployments, so
  every one of their on-chain pairs was reported stale; name-keyed lookup also
  cannot represent the several distinct Composer addresses per network. Warn,
  rather than silently skip, when an entry resolves to nothing, and warn when a
  config address disagrees with one we did deploy. Fixes 22 networks.
- core facets: add CORE_FACET_EXEMPTIONS so a facet can be core going forward.
  LiFiIntentEscrowFacet stays in coreFacets, so newly onboarded networks are
  enforced by default, while the networks that predate that decision are exempt
  until the facet is backfilled. Table is validated in tests and the exemption
  is printed per network. Fixes 58 networks.
- remove the feecollector-owner invariant: FeeCollector is deprecated and its
  owner is no longer maintained against config.feeCollectorOwner.
- drop ReceiverAcrossV3 from the receiver/executor binding check: deprecated in
  favour of ReceiverAcrossV4, which stays checked along with the other three.
- invariant banner no longer prints its severity as a `[error]`-style prefix. It
  runs once per invariant per network, so the token made every passing check
  match a grep for '[error]' and made a healthy run read as mass failure.

Verified against production RPCs: katana (previously stale-pair failure) and
flow (previously missing-facet failure) both pass; xdc no longer runs the
FeeCollector owner check.

Not addressed here (real drift, tracked separately): ERC20Proxy owner on 6
networks, and 8 networks with genuinely missing facets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lifi-action-bot
lifi-action-bot marked this pull request as draft July 27, 2026 01:58
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Health checks now support network-specific core-facet exemptions, authoritative whitelist periphery addresses, updated facet derivation, deprecated-check removal, improved Tron handling, refreshed deployment state, and new zkSync facet update scripts.

Changes

Health-check and deployment updates

Layer / File(s) Summary
Facet configuration and target state
config/global.json, script/deploy/shared/*, script/deploy/_targetState.json
Updates core/periphery definitions, adds and tests non-core facet derivation, and refreshes facet versions and entries across network target states.
Core-facet exemptions
script/deploy/healthCheckInvariants.ts, script/deploy/healthCheckInvariants.test.ts, script/deploy/healthCheck.ts
Adds network-specific exemption definitions and lookup, validates the exemption table, and excludes exempt facets during health checks.
Whitelist validation and receiver bindings
script/deploy/healthCheckInvariants.ts, script/deploy/healthCheckInvariants.test.ts
Uses configured periphery addresses for expected selector pairs, emits mismatch or missing-address warnings, and stops checking deprecated ReceiverAcrossV3.
Deprecated invariant and runner cleanup
script/deploy/healthCheck.ts, script/deploy/healthCheckInvariants.ts
Removes FeeCollector ownership data and its invariant, and updates invariant banner formatting.
Tron address parsing
script/deploy/tron/*, script/deploy/healthCheckInvariants.ts
Parses Tron addresses from diagnostic output and uses the parser in periphery registration checks.
Deployment records
deployments/*
Adds deployment metadata and updates Avalanche, Metis, and zkSync facet deployment maps.
zkSync update scripts
script/deploy/zksync/*.s.sol
Adds update entrypoints for CBridgeFacetPacked, CalldataVerificationFacet, and SymbiosisFacet.
Tron network access
.github/workflows/healthCheckAllNetworks.yml, script/troncast/utils/tronweb.ts
Passes TronGrid API credentials to health-check and TronWeb requests when applicable.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the health-check false-failure fixes and target-state refresh, matching the PR scope.
Description check ✅ Passed The description follows the template and includes the task, rationale, checklists, and verification details, with only minor checklist items left unfilled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/healthcheck-false-failures

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@0xDEnYO
0xDEnYO marked this pull request as ready for review July 27, 2026 01:58

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
script/deploy/healthCheckInvariants.ts (2)

191-198: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider readonly for the exemption fields.

ICoreFacetExemption describes a static, never-mutated configuration table. Marking its fields readonly would make that intent explicit and prevent accidental mutation.

♻️ Proposed diff
 export interface ICoreFacetExemption {
   /** Facet name as listed in `config/global.json` → `coreFacets`. */
-  facet: string
+  readonly facet: string
   /** Why these networks are exempt, including the ticket that documents the decision. */
-  reason: string
+  readonly reason: string
   /** Network keys (as in config/networks.json) that predate the facet becoming core. */
-  networks: string[]
+  readonly networks: readonly string[]
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/healthCheckInvariants.ts` around lines 191 - 198, Update the
ICoreFacetExemption interface so facet, reason, and networks are readonly,
reflecting that exemption configuration is static and preventing accidental
mutation.

Source: Coding guidelines


500-544: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Isolate per-entry failures so one bad config address doesn't blank out all expected pairs.

getAddress(contractAddr as Address) throws on a malformed address; since the periphery loop shares the outer try/catch with the DEXS loop, one bad config/whitelist.json entry (now a human-edited source, unlike the deployments file) would discard every already-computed DEX pair and return [], silently disabling whitelist checks for the whole network for that run instead of just excluding/warning on the one bad entry.

♻️ Proposed approach

Wrap just the per-peripheryContract body in its own try/catch, calling logWarn/logError for that entry and continue, so a single malformed entry doesn't blank out unrelated DEX/periphery pairs already collected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/healthCheckInvariants.ts` around lines 500 - 544, Isolate
processing of each peripheryContract in the periphery loop so malformed
addresses cannot abort the outer health-check flow. Wrap the per-entry address
resolution and selector iteration, including getAddress, in a try/catch; log a
warning or error identifying the affected entry, then continue to the next
peripheryContract while preserving already collected DEX and periphery expected
pairs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@script/deploy/healthCheckInvariants.ts`:
- Around line 504-540: Update the mismatch warning in the periphery whitelist
loop around `networkPeripheryContracts` and
`deployedContracts[peripheryContract.name]` so it is skipped when multiple
entries share the same name on the network. Keep the config address as the
source of truth and retain the warning for uniquely named entries where the
deployed address can be compared unambiguously.

---

Nitpick comments:
In `@script/deploy/healthCheckInvariants.ts`:
- Around line 191-198: Update the ICoreFacetExemption interface so facet,
reason, and networks are readonly, reflecting that exemption configuration is
static and preventing accidental mutation.
- Around line 500-544: Isolate processing of each peripheryContract in the
periphery loop so malformed addresses cannot abort the outer health-check flow.
Wrap the per-entry address resolution and selector iteration, including
getAddress, in a try/catch; log a warning or error identifying the affected
entry, then continue to the next peripheryContract while preserving already
collected DEX and periphery expected pairs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 22861d7a-95e9-475f-98fe-f03d4213c996

📥 Commits

Reviewing files that changed from the base of the PR and between 4fd47cb and a3a51e2.

📒 Files selected for processing (3)
  • script/deploy/healthCheck.ts
  • script/deploy/healthCheckInvariants.test.ts
  • script/deploy/healthCheckInvariants.ts

Comment thread script/deploy/healthCheckInvariants.ts
…d names

The staleness warning compared each periphery entry's config address against a
single deployedContracts[name] lookup. When several entries share one name on a
network — the Composer case this PR exists to support — at most one could match,
so the rest would warn spuriously. Skip the comparison when the name is not
unique on that network; deployedContracts cannot disambiguate it.

Latent today (Composer is the only duplicated name and is in no deployments
file, so the branch was unreachable), but it would fire the moment any shared
name got deployed.

Exports getExpectedPairs and covers the resolution rules with unit tests:
config address wins when the contract is undeployed, all same-name entries are
kept, no staleness warning on shared names, warning on a unique-name mismatch,
and a warning rather than a silent skip when nothing resolves.

Found by CodeRabbit on #2120.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
script/deploy/healthCheckInvariants.test.ts (2)

287-317: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use descriptive helper names and explicit return types.

Rename SEL, A, B, cfg, and run to describe their roles, and declare return types for the helper functions. As per coding guidelines, TypeScript functions need explicit return types and variables/functions must use descriptive names.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/healthCheckInvariants.test.ts` around lines 287 - 317, In the
test helpers, rename SEL, A, B, cfg, and run to descriptive role-based names,
and add explicit return types to the cfg and run helper functions. Keep their
existing behavior and signatures otherwise unchanged.

Source: Coding guidelines


358-363: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover deployed-address fallback when config is empty.

This only tests the fully unresolved path. Add a case with address: '' and a matching deployed entry, asserting that its selector pair is emitted without a reduced-coverage warning; that protects the configAddr || deployedAddr contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/healthCheckInvariants.test.ts` around lines 358 - 363, Add a
test alongside the existing unresolved case in the health-check invariant suite
using an empty config address and a matching deployed entry. Assert that the
resolved selector pair is emitted and no reduced-coverage warning is produced,
covering the configAddr || deployedAddr fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@script/deploy/healthCheckInvariants.test.ts`:
- Around line 287-317: In the test helpers, rename SEL, A, B, cfg, and run to
descriptive role-based names, and add explicit return types to the cfg and run
helper functions. Keep their existing behavior and signatures otherwise
unchanged.
- Around line 358-363: Add a test alongside the existing unresolved case in the
health-check invariant suite using an empty config address and a matching
deployed entry. Assert that the resolved selector pair is emitted and no
reduced-coverage warning is produced, covering the configAddr || deployedAddr
fallback behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f4ef7625-5371-404b-ac92-6d9be7a25e5e

📥 Commits

Reviewing files that changed from the base of the PR and between a3a51e2 and 7e4b185.

📒 Files selected for processing (2)
  • script/deploy/healthCheckInvariants.test.ts
  • script/deploy/healthCheckInvariants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • script/deploy/healthCheckInvariants.ts

0xDEnYO and others added 3 commits July 27, 2026 12:53
nonCoreFacets was derived by filtering target state against coreFacetsToCheck —
the core list AFTER per-network exclusions. So a core facet excluded for a network
(GasZip unsupported, or grandfathered via CORE_FACET_EXEMPTIONS) was absent from
that list, fell through the filter as "non-core", and was re-checked via target
state anyway. The exclusion printed, then the facet failed regardless.

Verified before the fix by adding LiFiIntentEscrowFacet to flow's target state:
the exemption logged and non-core-facets-deployed still reported it missing.
After the fix the same input passes.

This is why updating the exemption table alone cannot suppress the alerts when a
facet is listed in both coreFacets and target state.

Extracts the filter into deriveNonCoreFacets so the invariant is testable, and
documents that callers must pass the FULL core list. Six unit tests, including
one that pins the bug shape when a pre-excluded list is handed in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PROVISIONAL — pending confirmation that V2 fully replaces V1. Revert this commit
alone to undo; the two preceding fixes are independent of it.

Swaps coreFacets from LiFiIntentEscrowFacet to LiFiIntentEscrowFacetV2 and
retargets CORE_FACET_EXEMPTIONS to V2, recomputed from deployments: V2 is live on
10 production networks, so 63 are exempt until backfilled.

Needed because the exemption mechanism only covers CORE facets. Leaving V2
non-core while target state lists it on 75 networks would fail on the 63 where it
is not deployed, with nothing available to exempt them.

Verified against production RPCs with the refreshed target state applied: flow
(exempt) passes, katana (V2 deployed) checks and passes V2. Also verified with the
current unmodified target state so this commit stands alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d FeeCollector from corePeriphery

Folds the target-state refresh into this PR. The two changes are coupled and must
land together: target state now lists LiFiIntentEscrowFacetV2 on 75 networks, which
only passes because this PR makes V2 the core facet and exempts the 63 where it is
not yet deployed. Landing the refresh alone would fail those 63.

Target state regenerated from the production sheet (1995 -> 2056 entries):
- deprecations removed: FeeCollector (71 networks), LiFiDEXAggregator (70),
  ReceiverAcrossV3 (19), DexManagerFacet (3), GenericSwapFacet (tron)
- not-supported-there removals: DeBridgeDlnFacet/boba, SquidFacet/sei,
  AcrossFacetV4 + AcrossFacetPackedV4 + ArbitrumBridgeFacet + ReceiverAcrossV4/plume
- LiFiIntentEscrowFacet -> LiFiIntentEscrowFacetV2
- tron row added (was absent from the sheet, so a full refresh previously wiped
  tron's 20 entries to 0)
- 363 version upgrades, no downgrades

FeeCollector is deprecated, so it is removed from corePeriphery — otherwise
core-periphery-deployed keeps requiring it on every network regardless of target
state, which is the same false-failure class this PR removes.

Deliberately NOT removing FeeCollector or LiFiDEXAggregator from
whitelistPeripheryFunctions or config/whitelist.json: both are still whitelisted
on-chain (75 and 74 networks). Removing them from config first would report every
one of those pairs as "stale, not in config" — the exact failure this PR fixes.
They must be unwhitelisted on-chain first, then dropped from config.
(LiFiDEXAggregator was never in corePeriphery, so nothing to remove there.)

Verified against production RPCs: flow (was missing-facet), katana (was stale
pairs), xdc and botanix (were FeeCollector owner) all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@0xDEnYO 0xDEnYO changed the title fix(healthCheck): stop reporting false failures on 54 networks fix(healthCheck): eliminate false failures + refresh target state (68/71 networks red -> green) Jul 27, 2026
…m whitelistPeripheryFunctions

Both are deprecated. Removing them from whitelistPeripheryFunctions stops
updateWhitelistPeriphery / diamondUpdatePeriphery treating them as whitelist-eligible
and stops checkDeploymentAddressConsistency demanding whitelist.json coverage for them.

Safe with respect to whitelist-integrity: getExpectedPairs builds the expected pair
set from config/whitelist.json PERIPHERY, not from whitelistPeripheryFunctions, so
this cannot turn on-chain pairs into "stale, not in config". Verified — katana (20
pairs) and botanix (11 pairs) both still report the pair array synced.

Also safe with respect to periphery-registered: that invariant only considers
contracts present in the network's target state, and the target-state refresh in this
PR already removed both (FeeCollector on 71 networks, LiFiDEXAggregator on 70).

Still deliberately untouched: config/whitelist.json, which lists FeeCollector on 75
networks and LiFiDEXAggregator on 74 and matches what is whitelisted on-chain.
Removing them there before unwhitelisting on-chain would report ~149 pairs as stale.
Order has to be diamondSyncWhitelist first, then the config removal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lifi-qa-agent

lifi-qa-agent Bot commented Jul 27, 2026

Copy link
Copy Markdown

🔍 QA Review — EXSC-680 / PR #2120 (Post-Approval Re-Review — Run #24)

Ticket: EXSC-680 — Diamond health check: eliminate false failures (68/71 networks red) + refresh target state
PR: #2120fix/healthcheck-false-failuresmain
Author: 0xDEnYO
Reviewer: lifi-qa-agent[bot]
Date: 2026-07-28
Type: 🔁 Post-approval re-review — 2 commits pushed after Run #22 approval (06:19:11Z): merge from main + 31c373554eea


⚠️ New commits pushed after Run #22 approval — analysing post-approval changes only. The Run #22 analysis (tron parsing fix + whitelist bash wrapper fix) and all prior reviews remain valid.


Post-Approval Commits Reviewed

Commit Date Description
2ce864d627f9 2026-07-28T08:00:41Z Merge branch 'main' into fix/healthcheck-false-failures
31c373554eea 2026-07-28T12:58:44Z fix(healthCheck): exempt tempo from LiFiIntentEscrowFacetV2 core check

No src/ Solidity contract changes. AuditNotRequired label remains correct.


Post-Approval Code Review

2ce864d627f9 — Merge from main ✅

Routine merge to bring the branch up to date with main. No logic changes introduced; no conflicts reported. Standard housekeeping before the final commit.


31c373554eea — tempo exemption fix ✅

script/deploy/healthCheckInvariants.ts (+1/-0)

Single-line addition to CORE_FACET_EXEMPTIONS:

@@ -264,6 +264,7 @@ export const CORE_FACET_EXEMPTIONS: ICoreFacetExemption[] = [
       'swellchain',
       'taiko',
       'telos',
+      'tempo',
       'tron',
       'tronshasta',
       'unichain',

Context (from the inline review thread):

melianessa (SC reviewer) identified that tempo is:

  • status: active, type: mainnet in config/networks.json
  • Lists LiFiIntentEscrowFacetV2: 1.1.2 in _targetState.json
  • Has neither V1 nor V2 in deployments/tempo.json

Without this exemption, the core-facets-deployed invariant would flag tempo as red — LiFiIntentEscrowFacetV2 is a core facet (post-exemption list) and tempo is an active network where it has not yet been deployed, making it indistinguishable from the 63 other exempted networks.

0xDEnYO confirmed: "That's the identical not-yet-deployed scenario as the 63 other networks in the exemption list."

The fix is correct and minimal — tempo belongs in the exemption list alongside the other 63 active networks that predate the LiFiIntentEscrowFacetV2 becoming core. melianessa approved the PR (2026-07-28T13:04:03Z) immediately after this fix was merged.

Assessment: One-line targeted fix, correctly catching a regression that would have produced a false failure on tempo in the nightly health check. No test regression risk — the CORE_FACET_EXEMPTIONS table is validated by existing CI (versionControlAndAuditCheck.yml). ✅


✅ Verdict: Pass (Post-Approval Re-Review)

Both post-approval commits are clean: a routine main-branch merge followed by a targeted one-line exemption fix for tempo — caught by a human SC reviewer and fixed correctly. No Solidity contracts modified. AuditNotRequired label remains correct. No new blockers.

Carry-forward merge gate (unchanged from prior reviews):

⚠️ Do not merge until the backend team confirms the switch from LiFiIntentEscrowFacetV1 to LiFiIntentEscrowFacetV2. This is explicitly noted in the ticket: "LiFiIntentEscrowFacetV2 replacing V1 is confirmed by Alexander conditional on backend having switched over to V2." The config/target-state changes in this PR depend on that confirmation.

Human SC reviewer approval (melianessa, 2026-07-28T13:04:03Z) is on the PR and covers this final commit.


QA re-review by lifi-qa-agent[bot] — Run #24 · 2026-07-28 | Prior approvals: Run #22 (06:19:11Z), Run #21 (16:58:46Z), Run #20 (06:21:10Z)

lifi-qa-agent[bot]
lifi-qa-agent Bot previously approved these changes Jul 27, 2026

@lifi-qa-agent lifi-qa-agent 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.

QA Pass (Run #20) — TypeScript health check tooling fix: getExpectedPairs address resolution, deriveNonCoreFacets pre-exclusion list, CORE_FACET_EXEMPTIONS, deprecated check removal, and target state refresh all correct and well-tested. Merge gate: await backend confirmation of LiFiIntentEscrowFacetV2 switch-over before merging.

…tis CalldataVerificationFacet

Both are records drift, not on-chain drift. No on-chain change in this commit.

avalanche DiamondCutFacet: the diamond has 0xf7993A8d…58ABf registered, while
deployments/avalanche.json recorded 0xaD501185…957013, which is not among
facetAddresses(). Verified 0xf7993A8d is the genuine facet before repointing:
  - serves exactly one selector, 0x1f931c1c = diamondCut((address,uint8,bytes4[])[],address,bytes)
  - runtime bytecode is byte-identical to the recorded contract once the trailing
    solc metadata is excluded; both solc 0.8.17. Only the metadata hash differs,
    explained by optimizer runs (10000 vs 1000000)
  - Routescan reports it verified as ContractName "DiamondCutFacet"
Created 2022-10-18 (block 21218935), i.e. EARLIER than the recorded 2023-07-20
address: the diamond runs the original facet and the 2023 redeploy was never
registered. Both entries are kept in the deployment log, chronologically.

This went undetected because the avalanche.diamond.json facet entry had an empty
Name, and checkDeploymentAddressConsistency only indexes diamond facets that have
one — so the mismatch was invisible to it. Name/Version are now filled in, which
also clears the no-unexpected-facets warning for that address.

metis CalldataVerificationFacet: deployed and registered at 0x78659196…312E1, and
already correct in metis.diamond.json and the deployment log (v1.1.1, verified),
but missing from the flat deployments/metis.json the health check reads. Added.

Verified: consistency check passes; avalanche and metis both pass the health check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0xDEnYO and others added 5 commits July 27, 2026 16:05
…and PioneerFacet

Sheet re-parsed after two upstream edits. Only four entries change:
- blast/CBridgeFacetPacked (not supported there)
- PioneerFacet on arbitrum, mainnet, soneium (deprecated, confirmed by Alexander;
  its own deprecation PR is in flight)

No additions, no version changes. blast and mainnet now pass the health check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e blob

callTronContract prepends TronWeb's diagnostic lines ("⚙ Initializing TronWeb…",
"⚙ Calling <fn> on <addr>", …) to the actual return value, so the address is the
LAST meaningful line. parseTronAddressOutput trimmed the whole blob, which then
started with the first diagnostic line and failed every "is this a T… address"
check — so a correctly registered contract was reported as absent.

On tron this produced four false failures in periphery-registered (ERC20Proxy,
Executor, FeeForwarder, OutputValidator, each "got: null") while the registry
actually held the right addresses. Probed directly to confirm: getPeripheryContract
returns TDCo8wrq…dtcn9 / TUJnqSDo…kAbht / TBiwoFZp…dAF8, all matching deployments.

periphery-registered also had its own ad-hoc trim instead of the shared helper —
despite the Executor-binding branch documenting that it "mirror[s] the parsing used
by the periphery-registration check". Both now go through parseTronAddressOutput.

Six unit tests, including the exact diagnostic-prefixed output shape.

Verified against tron mainnet: all four periphery contracts now report registered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deployed at 0x2E09CeC715CB767F15c46aF8c33ff780eaAbFa74 via the standard path
(deploySingleContract -> DeployCalldataVerificationFacet.zksync.s.sol, pinned
foundry-zksync v0.0.32, zksolc + solc 0.8.29). Cost 0.000111 ETH.

Verified after deploy:
- runtime bytecode at the address is byte-identical to
  zkout/CalldataVerificationFacet.sol/CalldataVerificationFacet.json
- already verified on the zksync explorer
- deployment record written to MongoDB (contract-deployments)

zksync was the only production network missing this core facet. It is NOT yet
registered in the diamond — that needs a governance diamondCut, proposed separately.

Note: the deploy wrote the record to MongoDB only; deployments/_deployments_log_file.json
was not updated by the run (update-deployment-logs.ts syncs JSON -> MongoDB, not the
reverse), so the tracked master log does not yet contain this entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tron was the last network failing purely on rate limits: refund-wallet-access makes
one call per approved selector and TronGrid throttled anonymous traffic before the
check could finish, which reads as drift rather than reduced coverage.

The viem path already sends TRON-PRO-API-KEY via the devkit's
applyTronGridViemTransportExtras, but the health check's tron reads go through
callTronContract -> a `bun troncast` subprocess, which talks to TronWeb directly and
never saw the key. Wired it into initTronWeb using the devkit's own env var
(TRONGRID_API_KEY) and header constant, gated on isTronGridRpcUrl.

Also passes the secret to the nightly workflow, which previously supplied only
MONGODB_URI — so CI would have kept 429-ing regardless of local config.

Measured on tron mainnet: 429 count 43 -> 0. Tron's remaining failure is unrelated
(whitelist config load), tracked separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
diamondUpdateFacet resolves zksync scripts as script/deploy/zksync/<name>.zksync.s.sol
and aborts when the file is absent, so a facet with only an EVM update script cannot be
registered on zksync at all. That currently blocks registering CalldataVerificationFacet
v2.0.0, which is deployed on zksync but not yet in the diamond.

Adds update scripts for the three facets that are deployed on zksync but had none:
- CalldataVerificationFacet (core; blocks the pending registration)
- CBridgeFacetPacked
- SymbiosisFacet

Each is the standard one-line UpdateScriptBase wrapper, identical in structure to the
13 existing zksync update scripts (verified by diff against the EVM counterpart).

Not included, deliberately:
- CBridgeFacet and LiFiIntentEscrowFacetV2 are in zksync's target state but not deployed
  there, so update scripts alone would not help. CBridgeFacet is worth following up on:
  config/cbridge.json HAS a zksync entry and CBridgeFacetPacked is already deployed
  there, so the base facet looks like an oversight rather than an incompatibility — but
  it needs a deploy script plus a deployment, not just this.
- AccessManagerFacet, DiamondCutFacet, DiamondLoupeFacet, OwnershipFacet and
  PeripheryRegistryFacet are covered by UpdateCoreFacets.zksync.s.sol.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (5)
script/troncast/utils/tronweb.ts (1)

53-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the API-key gating branches.

Please add or confirm tests for a trimmed key with a TronGrid URL, a key with a non-TronGrid URL, and a blank key. These cases prevent regressions that silently restore anonymous health-check traffic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/troncast/utils/tronweb.ts` around lines 53 - 72, Add tests covering
the TronWeb initialization flow around the API-key header setup: verify a
trimmed TRONGRID_API_KEY is sent for TronGrid URLs, no header is sent for
non-TronGrid URLs, and blank or whitespace-only keys are omitted. Reuse the
existing TronWeb construction and RPC URL test utilities, and assert the
resulting headers for each branch.
script/deploy/zksync/UpdateCBridgeFacetPacked.zksync.s.sol (2)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a lowercase contract name.

DeployScript violates the repository rule requiring lowercase Solidity contract names. Rename this entrypoint consistently and update any Forge references that select DeployScript.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/zksync/UpdateCBridgeFacetPacked.zksync.s.sol` at line 7, Rename
the DeployScript contract to a lowercase-compliant name and update all Forge
configuration or invocation references that select DeployScript so the
deployment entrypoint remains unchanged.

Source: Coding guidelines


1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Apply the contract-naming rule consistently.

The three entrypoints declare contract DeployScript, but the Solidity guideline requires lowercase contract names. Rename each entrypoint contract in script/deploy/zksync/UpdateCBridgeFacetPacked.zksync.s.sol, UpdateCalldataVerificationFacet.zksync.s.sol, and UpdateSymbiosisFacet.zksync.s.sol, and update any Forge invocation/artifact lookup that targets DeployScript.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/zksync/UpdateCBridgeFacetPacked.zksync.s.sol` at line 1, Rename
the entrypoint contracts from DeployScript to lowercase names in
UpdateCBridgeFacetPacked.zksync.s.sol,
UpdateCalldataVerificationFacet.zksync.s.sol, and
UpdateSymbiosisFacet.zksync.s.sol, then update every corresponding Forge
invocation and artifact lookup to use the new contract names.

Source: Coding guidelines

script/deploy/zksync/UpdateCalldataVerificationFacet.zksync.s.sol (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a lowercase contract name.

DeployScript violates the repository rule requiring lowercase Solidity contract names. Rename this entrypoint consistently and update any Forge references that select DeployScript.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/zksync/UpdateCalldataVerificationFacet.zksync.s.sol` at line 7,
Rename the Solidity contract DeployScript to a lowercase contract name, and
update all Forge configuration or invocation references that select DeployScript
to use the new name consistently.

Source: Coding guidelines

script/deploy/zksync/UpdateSymbiosisFacet.zksync.s.sol (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a lowercase contract name.

DeployScript violates the repository rule requiring lowercase Solidity contract names. Rename this entrypoint consistently and update any Forge references that select DeployScript.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/zksync/UpdateSymbiosisFacet.zksync.s.sol` at line 7, Rename the
DeployScript contract to a lowercase name in the UpdateSymbiosisFacet deployment
entrypoint, and update all Forge configuration or invocation references that
select DeployScript to use the new name consistently.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@script/deploy/zksync/UpdateCalldataVerificationFacet.zksync.s.sol`:
- Line 7: Rename the Solidity contract DeployScript to a lowercase contract
name, and update all Forge configuration or invocation references that select
DeployScript to use the new name consistently.

In `@script/deploy/zksync/UpdateCBridgeFacetPacked.zksync.s.sol`:
- Line 7: Rename the DeployScript contract to a lowercase-compliant name and
update all Forge configuration or invocation references that select DeployScript
so the deployment entrypoint remains unchanged.
- Line 1: Rename the entrypoint contracts from DeployScript to lowercase names
in UpdateCBridgeFacetPacked.zksync.s.sol,
UpdateCalldataVerificationFacet.zksync.s.sol, and
UpdateSymbiosisFacet.zksync.s.sol, then update every corresponding Forge
invocation and artifact lookup to use the new contract names.

In `@script/deploy/zksync/UpdateSymbiosisFacet.zksync.s.sol`:
- Line 7: Rename the DeployScript contract to a lowercase name in the
UpdateSymbiosisFacet deployment entrypoint, and update all Forge configuration
or invocation references that select DeployScript to use the new name
consistently.

In `@script/troncast/utils/tronweb.ts`:
- Around line 53-72: Add tests covering the TronWeb initialization flow around
the API-key header setup: verify a trimmed TRONGRID_API_KEY is sent for TronGrid
URLs, no header is sent for non-TronGrid URLs, and blank or whitespace-only keys
are omitted. Reuse the existing TronWeb construction and RPC URL test utilities,
and assert the resulting headers for each branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a27754c-220c-4404-9788-3580a0f7ab58

📥 Commits

Reviewing files that changed from the base of the PR and between d3cc02c and 2425640.

📒 Files selected for processing (5)
  • .github/workflows/healthCheckAllNetworks.yml
  • script/deploy/zksync/UpdateCBridgeFacetPacked.zksync.s.sol
  • script/deploy/zksync/UpdateCalldataVerificationFacet.zksync.s.sol
  • script/deploy/zksync/UpdateSymbiosisFacet.zksync.s.sol
  • script/troncast/utils/tronweb.ts

lifi-qa-agent[bot]
lifi-qa-agent Bot previously approved these changes Jul 27, 2026

@lifi-qa-agent lifi-qa-agent 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.

QA Pass (Run #21, post-approval re-review) — 6 post-approval commits are clean tooling additions: tron parseTronAddressOutput fix is well-tested, TRONGRID_API_KEY wired correctly to CI and tronweb, zksync update scripts follow established repo convention, _targetState.json cleanup consistent with PioneerFacet deprecation. Merge gate carry-forward: await backend confirmation of LiFiIntentEscrowFacetV2 switch-over before merging.

…gnostics

The whitelist-integrity invariant parsed callTronContract's raw output for
getAllContractSelectorPairs() with .trim() + startsWith('['). That output is
prefixed by the troncast command echo ($ bun run ...) and TronWeb's diagnostic
lines -- one of which, "Formatted params: []", itself contains a '['. So the
trim left it starting with the echo line, startsWith('[') was false, and it
threw "Expected array format", which the outer catch swallowed into the generic
"Whitelist configuration not available" -- the last remaining tron failure.

Add a tested shared helper parseTroncastArrayOutput that strips the echo and
diagnostic lines before locating the bracketed payload (mirroring the fix
already applied to parseTronAddressOutput), and surface the real error/stack in
the catch so future throws are diagnosable rather than masked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t source-into-zsh

The stale/missing-pair remediation printed
`source script/tasks/diamondSyncWhitelist.sh && diamondSyncWhitelist <net> <env>`.
Sourcing runs the #!/bin/bash script's body in the caller's interactive shell, and
its `read -ra` (a bash-only `read` option) fails under zsh -- the macOS default --
with "bad option: -a", leaving the network list empty so the sync runs against
`./deployments/.json`. Point at ./script/tasks/syncWhitelistToNetworks.sh instead,
which executes under its own bash shebang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lifi-qa-agent[bot]
lifi-qa-agent Bot previously approved these changes Jul 28, 2026

@lifi-qa-agent lifi-qa-agent 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.

QA Pass (Run #22, post-approval re-review) — 2 post-approval commits: tron parseTroncastArrayOutput fix (well-tested) and whitelist bash wrapper remediation hint. No Solidity changes.

@0xDEnYO
0xDEnYO enabled auto-merge (squash) July 28, 2026 08:05
Comment thread script/deploy/healthCheckInvariants.ts
tempo is active/mainnet with LiFiIntentEscrowFacetV2 in its target state but
the facet is not yet deployed there, so core-facets-deployed would go red —
the same not-yet-backfilled situation as the other exempted networks. Add it
to CORE_FACET_EXEMPTIONS so the check stays green with a printed exemption
until the facet is backfilled, rather than reintroducing a red the PR is
meant to eliminate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@0xDEnYO
0xDEnYO merged commit 335c3a6 into main Jul 28, 2026
42 of 46 checks passed
@0xDEnYO
0xDEnYO deleted the fix/healthcheck-false-failures branch July 28, 2026 13:06

@lifi-qa-agent lifi-qa-agent 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.

QA Pass (Run #24, post-approval re-review) — 2 new commits: routine merge from main + single-line tempo exemption added to CORE_FACET_EXEMPTIONS (caught by melianessa, verified correct: tempo is active/mainnet with V2 in target state but not yet deployed — identical pattern to the 63 other exempted networks). Merge gate carry-forward: await backend confirmation of LiFiIntentEscrowFacetV2 switch-over.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants