Skip to content

feat: Add xlayer integration tests - #177

Merged
lucas-manuel merged 3 commits into
feat/sc-1573-spell-20260716from
add-xlayer-integration
Jul 7, 2026
Merged

feat: Add xlayer integration tests#177
lucas-manuel merged 3 commits into
feat/sc-1573-spell-20260716from
add-xlayer-integration

Conversation

@lucas-manuel

@lucas-manuel lucas-manuel commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added support for two additional chains, expanding execution and payload handling across the app.
    • Introduced new mainnet governance actions for bridging, rate limits, and treasury transfers tied to those chains.
  • Bug Fixes
    • Updated vault and transfer settings to better align with current deployment behavior.
  • Tests
    • Added broader end-to-end coverage for cross-chain execution, configuration checks, and payload verification.

@lucas-manuel lucas-manuel self-assigned this Jul 7, 2026
@octane-security-app

Copy link
Copy Markdown

Summary by Octane

New Contracts

  • SparkEthereum_20260716.sol: The smart contract deactivates an old USDT vault, enables USDT and USDG cross-chain bridging, and manages allocations for fees and incentives.
  • SparkRobinhood_20260716.sol: The smart contract updates access roles and sets rate limits for asset transfers related to Spark Robinhood as of July 16, 2026.

Updated Contracts

  • SparkPayloadEthereum.sol: Added support for Robinhood and XLayer networks, enabling message forwarding to these new targets.
  • ChainIdUtils.sol: Added support for Robinhood and XLayer chain IDs with corresponding utility functions.
  • SpellRunner.sol: The smart contract now supports Robinhood and XLayer chains, adding their executors, domains, and bridges.
  • SpellTests.sol: Added functionality to test payload bytecode matches for Robinhood and XLayer chains.

🔗 Commit Hash: 32ee908

@lucas-manuel
lucas-manuel changed the base branch from master to feat/sc-1573-spell-20260716 July 7, 2026 17:47
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8d1d376f-2735-4668-853d-66084c96cbcd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR adds support for two new chains, Robinhood and XLayer, across the codebase: chain-id utilities, payload dispatch logic in SparkPayloadEthereum, new proposal contracts (SparkEthereum_20260716, SparkRobinhood_20260716), test-harness fork/bridge/executor wiring, and extensive test suites. Also updates submodule pointers and CI config.

Changes

Robinhood/XLayer chain integration

Layer / File(s) Summary
Chain-id utilities and vault interface
src/libraries/ChainIdUtils.sol, src/interfaces/Interfaces.sol
Adds Robinhood (4663) and XLayer (196) chain-id-to-domain mappings and helper functions; adds getImplementation() accessor to ISparkVaultV2Like.
Payload dispatch in SparkPayloadEthereum
src/SparkPayloadEthereum.sol
Adds PAYLOAD_ROBINHOOD/PAYLOAD_XLAYER immutables, chain address imports, and execute() branches queuing cross-chain messages via forwarders; minor formatting change in _sendArbTokens.
Ethereum spell payload
src/proposals/20260716/SparkEthereum_20260716.sol
New contract configuring USDG/USDT bridging rate limits, deactivating an old Morpho USDT vault, and transferring USDS to multiple treasury recipients.
Robinhood spell payload
src/proposals/20260716/SparkRobinhood_20260716.sol
New contract managing FREEZER/RELAYER roles on ForeignController and setting a USDG transfer rate limit.
Ethereum/Robinhood spell tests
src/proposals/20260716/Spell_20260716.t.sol
New test contracts validating vault rate-limit deactivation, Robinhood role changes, and USDS treasury balance transfers.
XLayer config, E2E, and crosschain tests
src/proposals/20260716/XLayer_20260716.t.sol
New tests for post-deploy config, deposit cap/VSR E2E flows, and a crosschain payload flow relaying an L1 message to update vault VSR bounds on XLayer.
Test-harness chain wiring
src/test-harness/SpellRunner.sol, src/test-harness/SpellTests.sol
Adds fork setup, executor/bridge config, foreign payload lookup, Etherscan handling for both chains, and bytecode-match tests.
CI and submodule updates
.github/workflows/test.yml, lib/spark-address-registry, lib/xchain-helpers, diffs/...md
Adds RH_RPC_URL env var, bumps submodule pointers, adds empty diff artifact.

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

Possibly related PRs

Suggested labels: Priority: Medium, Status: Ready for Review

Suggested reviewers: certora-spark-review, TheMj0ln1r, deluca-mike

Poem

Two new burrows, Robinhood and XLayer wide,
Payloads hop across the bridge with pride 🐇
Rate limits set, roles swapped just right,
USDS scurries off into the night.
A submodule bump, a workflow tweak too—
This rabbit's proposal stack sees it through! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is descriptive and properly prefixed, but it does not end with the required SC- identifier in parentheses. Append the Linear issue reference, e.g. feat: Add xlayer integration tests (SC-1234).
Description check ⚠️ Warning The description is missing entirely, so none of the required template sections or deployment conditions are provided. Add the full template content, including Forum Post, Dependencies, Addresses, Notes, and all checklist sections.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-xlayer-integration

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/test-harness/SpellRunner.sol (1)

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

Hardcoded XLayer RPC URL breaks established env-var pattern.

Every other chain (including the newly added Robinhood chain) sources its RPC URL via vm.envString(...), but XLayer is hardcoded to "https://rpc.xlayer.tech". This public endpoint is rate-limited (100 req/s per IP per X Layer docs) and not overridable, unlike Robinhood's RH_RPC_URL which was added to CI. This makes XLayer tests more prone to CI flakiness/rate-limiting and inconsistent with the rest of the harness.

♻️ Suggested fix
         setChain("xlayer", ChainData({
             name    : "XLayer",
-            rpcUrl  : "https://rpc.xlayer.tech",
+            rpcUrl  : vm.envOr("XLAYER_RPC_URL", string("https://rpc.xlayer.tech")),
             chainId : 196
         }));
🤖 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 `@src/test-harness/SpellRunner.sol` around lines 100 - 110, The XLayer chain
setup in SpellRunner currently hardcodes the RPC endpoint, unlike the rest of
the harness that uses vm.envString for overridable URLs. Update the xlayer entry
in setChain to read the RPC URL from an environment variable, following the same
pattern used for robinhood_chain and the other chain definitions, so CI and
local runs can avoid the public rate-limited endpoint.
src/proposals/20260716/Spell_20260716.t.sol (2)

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

Test contract names reference the wrong spell date (20260702).

All three test contracts are named SparkEthereum_20260702_* but target _spellId = 20260716. Rename to 20260716 for consistency and to avoid confusion with the prior spell.

Also applies to: 96-96, 111-111

🤖 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 `@src/proposals/20260716/Spell_20260716.t.sol` at line 64, The three test
contract names still use the old spell date, which conflicts with the current
_spellId value. Rename each SparkEthereum_20260702_* test contract to
SparkEthereum_20260716_* in the affected proposal test file so the contract
names match the 20260716 spell and avoid confusion with the prior spell.

136-150: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

RELAYER role changes are not asserted.

SparkRobinhood_20260716.execute() also grants RELAYER to ALM_BACKSTOP_RELAYER_MULTISIG and revokes it from ALM_RELAYER_MULTISIG_1, but this test only verifies the FREEZER role transitions. Consider adding parallel assertions for RELAYER to cover the full payload.

🤖 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 `@src/proposals/20260716/Spell_20260716.t.sol` around lines 136 - 150, The
Robinhood role-change test only checks FREEZER transitions, so it misses the
RELAYER changes applied by SparkRobinhood_20260716.execute(). Update
test_ROBINHOOD_controllerRoleChanges in Spell_20260716.t.sol to add matching
assertions for controller.RELAYER() before and after
_executeAllPayloadsAndBridges(), verifying ALM_RELAYER_MULTISIG_1 is revoked and
ALM_BACKSTOP_RELAYER_MULTISIG is granted.
src/proposals/20260716/SparkEthereum_20260716.sol (1)

26-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Multiple placeholder values still marked // TODO: change.

ANCHORAGE_FEES_RECIPIENT, INCENTIVES_RECIPIENT, XLAYER_ALM_PROXY, and LZ_ENDPOINT_XLAYER are placeholders. These must be finalized before this spell can be executed on mainnet. Want me to open a tracking issue?

Also applies to: 38-38

🤖 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 `@src/proposals/20260716/SparkEthereum_20260716.sol` around lines 26 - 30, The
proposal still contains placeholder constants marked TODO, so finalize the real
mainnet values before execution. Update SparkEthereum_20260716.sol by replacing
ANCHORAGE_FEES_RECIPIENT, INCENTIVES_RECIPIENT, and XLAYER_ALM_PROXY with their
production addresses, and also resolve LZ_ENDPOINT_XLAYER wherever it is defined
in the same contract. Keep the existing constant names and only swap in the
verified values so the deploy/execution path no longer relies on placeholders.
src/proposals/20260716/XLayer_20260716.t.sol (1)

72-73: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid hardcoding the XLayer RPC URL in fork tests

vm.createSelectFork("https://rpc.xlayer.tech", 64662930) is used in both XLayerConfigTests and XLayerE2ETests, and the same URL is also embedded in XLayerCrosschainE2ETests. That couples CI to a public endpoint and a fixed fork block, which can make these tests flaky. Source the RPC from config/env like the other networks and move the block number into a shared constant.

🤖 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 `@src/proposals/20260716/XLayer_20260716.t.sol` around lines 72 - 73, The fork
setup in XLayerConfigTests, XLayerE2ETests, and XLayerCrosschainE2ETests is
hardcoding the public XLayer RPC and a fixed block, which should be replaced
with configurable values. Update the setUp path that calls vm.createSelectFork
to read the RPC URL from the same config/env mechanism used by the other
networks, and move the fork block number into a shared constant so all XLayer
tests use the same source of truth.
🤖 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 `@src/proposals/20260716/SparkEthereum_20260716.sol`:
- Around line 26-27: `INCENTIVES_RECIPIENT` and `ANCHORAGE_FEES_RECIPIENT` are
both using the same placeholder value, so the transfers in the proposal
currently route to one address and break the treasury test. Update the constants
in `SparkEthereum_20260716` to use two distinct, real recipient addresses before
merge, and verify the balance expectations in
`test_ETHEREUM_sparkTreasury_transfers` still match the separate incentive and
anchorage-fees transfers.
- Around line 40-41: The constructor in SparkEthereum_20260716 currently leaves
the inherited payload addresses unset, so the PAYLOAD_ROBINHOOD and
PAYLOAD_XLAYER branches in SparkPayloadEthereum.execute() never activate. Update
the constructor to accept the Robinhood and XLayer payload addresses and assign
them to the corresponding inherited immutables/fields so those dispatch paths
are enabled. Make sure the existing execute() checks will now see nonzero values
and queue both payloads correctly.

In `@src/proposals/20260716/XLayer_20260716.t.sol`:
- Line 99: The comments in this test suite still refer to USDG/SPUSDG even
though the exercised vault is the USDT-based spUSDT vault, so update the
affected comments in the XLayer_20260716_t.sol scenario to match the actual
asset and vault names. In particular, revise the placeholders around the role
setup, vault state, and withdrawal sections so they reference USDT/Spark Savings
USDT instead of USDG/SPUSDG, and fix the deposit-state comment in the section
around the vault balance check to describe the 1e6 totalAssets/totalSupply
assertion rather than an 8.01e6 USDG amount.

---

Nitpick comments:
In `@src/proposals/20260716/SparkEthereum_20260716.sol`:
- Around line 26-30: The proposal still contains placeholder constants marked
TODO, so finalize the real mainnet values before execution. Update
SparkEthereum_20260716.sol by replacing ANCHORAGE_FEES_RECIPIENT,
INCENTIVES_RECIPIENT, and XLAYER_ALM_PROXY with their production addresses, and
also resolve LZ_ENDPOINT_XLAYER wherever it is defined in the same contract.
Keep the existing constant names and only swap in the verified values so the
deploy/execution path no longer relies on placeholders.

In `@src/proposals/20260716/Spell_20260716.t.sol`:
- Line 64: The three test contract names still use the old spell date, which
conflicts with the current _spellId value. Rename each SparkEthereum_20260702_*
test contract to SparkEthereum_20260716_* in the affected proposal test file so
the contract names match the 20260716 spell and avoid confusion with the prior
spell.
- Around line 136-150: The Robinhood role-change test only checks FREEZER
transitions, so it misses the RELAYER changes applied by
SparkRobinhood_20260716.execute(). Update test_ROBINHOOD_controllerRoleChanges
in Spell_20260716.t.sol to add matching assertions for controller.RELAYER()
before and after _executeAllPayloadsAndBridges(), verifying
ALM_RELAYER_MULTISIG_1 is revoked and ALM_BACKSTOP_RELAYER_MULTISIG is granted.

In `@src/proposals/20260716/XLayer_20260716.t.sol`:
- Around line 72-73: The fork setup in XLayerConfigTests, XLayerE2ETests, and
XLayerCrosschainE2ETests is hardcoding the public XLayer RPC and a fixed block,
which should be replaced with configurable values. Update the setUp path that
calls vm.createSelectFork to read the RPC URL from the same config/env mechanism
used by the other networks, and move the fork block number into a shared
constant so all XLayer tests use the same source of truth.

In `@src/test-harness/SpellRunner.sol`:
- Around line 100-110: The XLayer chain setup in SpellRunner currently hardcodes
the RPC endpoint, unlike the rest of the harness that uses vm.envString for
overridable URLs. Update the xlayer entry in setChain to read the RPC URL from
an environment variable, following the same pattern used for robinhood_chain and
the other chain definitions, so CI and local runs can avoid the public
rate-limited endpoint.
🪄 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

Run ID: f3c7c161-c91d-4693-b9ab-125de5275b5c

📥 Commits

Reviewing files that changed from the base of the PR and between 17b5536 and 32ee908.

📒 Files selected for processing (20)
  • .github/workflows/test.yml
  • archive/20260702/SparkArbitrumOne_20260702.sol
  • archive/20260702/SparkAvalanche_20260702.sol
  • archive/20260702/SparkBase_20260702.sol
  • archive/20260702/SparkEthereum_20260702.sol
  • archive/20260702/SparkOptimism_20260702.sol
  • archive/20260702/SparkUnichain_20260702.sol
  • archive/20260702/Spell_20260702.t.sol
  • diffs/20260716-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-pre_20260716-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-post.md
  • lib/spark-address-registry
  • lib/xchain-helpers
  • src/SparkPayloadEthereum.sol
  • src/interfaces/Interfaces.sol
  • src/libraries/ChainIdUtils.sol
  • src/proposals/20260716/SparkEthereum_20260716.sol
  • src/proposals/20260716/SparkRobinhood_20260716.sol
  • src/proposals/20260716/Spell_20260716.t.sol
  • src/proposals/20260716/XLayer_20260716.t.sol
  • src/test-harness/SpellRunner.sol
  • src/test-harness/SpellTests.sol

@coderabbitai coderabbitai 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/test-harness/SpellRunner.sol (1)

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

Hardcoded XLayer RPC URL breaks established env-var pattern.

Every other chain (including the newly added Robinhood chain) sources its RPC URL via vm.envString(...), but XLayer is hardcoded to "https://rpc.xlayer.tech". This public endpoint is rate-limited (100 req/s per IP per X Layer docs) and not overridable, unlike Robinhood's RH_RPC_URL which was added to CI. This makes XLayer tests more prone to CI flakiness/rate-limiting and inconsistent with the rest of the harness.

♻️ Suggested fix
         setChain("xlayer", ChainData({
             name    : "XLayer",
-            rpcUrl  : "https://rpc.xlayer.tech",
+            rpcUrl  : vm.envOr("XLAYER_RPC_URL", string("https://rpc.xlayer.tech")),
             chainId : 196
         }));
🤖 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 `@src/test-harness/SpellRunner.sol` around lines 100 - 110, The XLayer chain
setup in SpellRunner currently hardcodes the RPC endpoint, unlike the rest of
the harness that uses vm.envString for overridable URLs. Update the xlayer entry
in setChain to read the RPC URL from an environment variable, following the same
pattern used for robinhood_chain and the other chain definitions, so CI and
local runs can avoid the public rate-limited endpoint.
src/proposals/20260716/Spell_20260716.t.sol (2)

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

Test contract names reference the wrong spell date (20260702).

All three test contracts are named SparkEthereum_20260702_* but target _spellId = 20260716. Rename to 20260716 for consistency and to avoid confusion with the prior spell.

Also applies to: 96-96, 111-111

🤖 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 `@src/proposals/20260716/Spell_20260716.t.sol` at line 64, The three test
contract names still use the old spell date, which conflicts with the current
_spellId value. Rename each SparkEthereum_20260702_* test contract to
SparkEthereum_20260716_* in the affected proposal test file so the contract
names match the 20260716 spell and avoid confusion with the prior spell.

136-150: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

RELAYER role changes are not asserted.

SparkRobinhood_20260716.execute() also grants RELAYER to ALM_BACKSTOP_RELAYER_MULTISIG and revokes it from ALM_RELAYER_MULTISIG_1, but this test only verifies the FREEZER role transitions. Consider adding parallel assertions for RELAYER to cover the full payload.

🤖 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 `@src/proposals/20260716/Spell_20260716.t.sol` around lines 136 - 150, The
Robinhood role-change test only checks FREEZER transitions, so it misses the
RELAYER changes applied by SparkRobinhood_20260716.execute(). Update
test_ROBINHOOD_controllerRoleChanges in Spell_20260716.t.sol to add matching
assertions for controller.RELAYER() before and after
_executeAllPayloadsAndBridges(), verifying ALM_RELAYER_MULTISIG_1 is revoked and
ALM_BACKSTOP_RELAYER_MULTISIG is granted.
src/proposals/20260716/SparkEthereum_20260716.sol (1)

26-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Multiple placeholder values still marked // TODO: change.

ANCHORAGE_FEES_RECIPIENT, INCENTIVES_RECIPIENT, XLAYER_ALM_PROXY, and LZ_ENDPOINT_XLAYER are placeholders. These must be finalized before this spell can be executed on mainnet. Want me to open a tracking issue?

Also applies to: 38-38

🤖 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 `@src/proposals/20260716/SparkEthereum_20260716.sol` around lines 26 - 30, The
proposal still contains placeholder constants marked TODO, so finalize the real
mainnet values before execution. Update SparkEthereum_20260716.sol by replacing
ANCHORAGE_FEES_RECIPIENT, INCENTIVES_RECIPIENT, and XLAYER_ALM_PROXY with their
production addresses, and also resolve LZ_ENDPOINT_XLAYER wherever it is defined
in the same contract. Keep the existing constant names and only swap in the
verified values so the deploy/execution path no longer relies on placeholders.
src/proposals/20260716/XLayer_20260716.t.sol (1)

72-73: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid hardcoding the XLayer RPC URL in fork tests

vm.createSelectFork("https://rpc.xlayer.tech", 64662930) is used in both XLayerConfigTests and XLayerE2ETests, and the same URL is also embedded in XLayerCrosschainE2ETests. That couples CI to a public endpoint and a fixed fork block, which can make these tests flaky. Source the RPC from config/env like the other networks and move the block number into a shared constant.

🤖 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 `@src/proposals/20260716/XLayer_20260716.t.sol` around lines 72 - 73, The fork
setup in XLayerConfigTests, XLayerE2ETests, and XLayerCrosschainE2ETests is
hardcoding the public XLayer RPC and a fixed block, which should be replaced
with configurable values. Update the setUp path that calls vm.createSelectFork
to read the RPC URL from the same config/env mechanism used by the other
networks, and move the fork block number into a shared constant so all XLayer
tests use the same source of truth.
🤖 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 `@src/proposals/20260716/SparkEthereum_20260716.sol`:
- Around line 26-27: `INCENTIVES_RECIPIENT` and `ANCHORAGE_FEES_RECIPIENT` are
both using the same placeholder value, so the transfers in the proposal
currently route to one address and break the treasury test. Update the constants
in `SparkEthereum_20260716` to use two distinct, real recipient addresses before
merge, and verify the balance expectations in
`test_ETHEREUM_sparkTreasury_transfers` still match the separate incentive and
anchorage-fees transfers.
- Around line 40-41: The constructor in SparkEthereum_20260716 currently leaves
the inherited payload addresses unset, so the PAYLOAD_ROBINHOOD and
PAYLOAD_XLAYER branches in SparkPayloadEthereum.execute() never activate. Update
the constructor to accept the Robinhood and XLayer payload addresses and assign
them to the corresponding inherited immutables/fields so those dispatch paths
are enabled. Make sure the existing execute() checks will now see nonzero values
and queue both payloads correctly.

In `@src/proposals/20260716/XLayer_20260716.t.sol`:
- Line 99: The comments in this test suite still refer to USDG/SPUSDG even
though the exercised vault is the USDT-based spUSDT vault, so update the
affected comments in the XLayer_20260716_t.sol scenario to match the actual
asset and vault names. In particular, revise the placeholders around the role
setup, vault state, and withdrawal sections so they reference USDT/Spark Savings
USDT instead of USDG/SPUSDG, and fix the deposit-state comment in the section
around the vault balance check to describe the 1e6 totalAssets/totalSupply
assertion rather than an 8.01e6 USDG amount.

---

Nitpick comments:
In `@src/proposals/20260716/SparkEthereum_20260716.sol`:
- Around line 26-30: The proposal still contains placeholder constants marked
TODO, so finalize the real mainnet values before execution. Update
SparkEthereum_20260716.sol by replacing ANCHORAGE_FEES_RECIPIENT,
INCENTIVES_RECIPIENT, and XLAYER_ALM_PROXY with their production addresses, and
also resolve LZ_ENDPOINT_XLAYER wherever it is defined in the same contract.
Keep the existing constant names and only swap in the verified values so the
deploy/execution path no longer relies on placeholders.

In `@src/proposals/20260716/Spell_20260716.t.sol`:
- Line 64: The three test contract names still use the old spell date, which
conflicts with the current _spellId value. Rename each SparkEthereum_20260702_*
test contract to SparkEthereum_20260716_* in the affected proposal test file so
the contract names match the 20260716 spell and avoid confusion with the prior
spell.
- Around line 136-150: The Robinhood role-change test only checks FREEZER
transitions, so it misses the RELAYER changes applied by
SparkRobinhood_20260716.execute(). Update test_ROBINHOOD_controllerRoleChanges
in Spell_20260716.t.sol to add matching assertions for controller.RELAYER()
before and after _executeAllPayloadsAndBridges(), verifying
ALM_RELAYER_MULTISIG_1 is revoked and ALM_BACKSTOP_RELAYER_MULTISIG is granted.

In `@src/proposals/20260716/XLayer_20260716.t.sol`:
- Around line 72-73: The fork setup in XLayerConfigTests, XLayerE2ETests, and
XLayerCrosschainE2ETests is hardcoding the public XLayer RPC and a fixed block,
which should be replaced with configurable values. Update the setUp path that
calls vm.createSelectFork to read the RPC URL from the same config/env mechanism
used by the other networks, and move the fork block number into a shared
constant so all XLayer tests use the same source of truth.

In `@src/test-harness/SpellRunner.sol`:
- Around line 100-110: The XLayer chain setup in SpellRunner currently hardcodes
the RPC endpoint, unlike the rest of the harness that uses vm.envString for
overridable URLs. Update the xlayer entry in setChain to read the RPC URL from
an environment variable, following the same pattern used for robinhood_chain and
the other chain definitions, so CI and local runs can avoid the public
rate-limited endpoint.
🪄 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

Run ID: f3c7c161-c91d-4693-b9ab-125de5275b5c

📥 Commits

Reviewing files that changed from the base of the PR and between 17b5536 and 32ee908.

📒 Files selected for processing (20)
  • .github/workflows/test.yml
  • archive/20260702/SparkArbitrumOne_20260702.sol
  • archive/20260702/SparkAvalanche_20260702.sol
  • archive/20260702/SparkBase_20260702.sol
  • archive/20260702/SparkEthereum_20260702.sol
  • archive/20260702/SparkOptimism_20260702.sol
  • archive/20260702/SparkUnichain_20260702.sol
  • archive/20260702/Spell_20260702.t.sol
  • diffs/20260716-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-pre_20260716-Ethereum-0xC13e21B648A5Ee794902342038FF3aDAB66BE987-post.md
  • lib/spark-address-registry
  • lib/xchain-helpers
  • src/SparkPayloadEthereum.sol
  • src/interfaces/Interfaces.sol
  • src/libraries/ChainIdUtils.sol
  • src/proposals/20260716/SparkEthereum_20260716.sol
  • src/proposals/20260716/SparkRobinhood_20260716.sol
  • src/proposals/20260716/Spell_20260716.t.sol
  • src/proposals/20260716/XLayer_20260716.t.sol
  • src/test-harness/SpellRunner.sol
  • src/test-harness/SpellTests.sol
🛑 Comments failed to post (3)
src/proposals/20260716/SparkEthereum_20260716.sol (2)

26-27: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

INCENTIVES_RECIPIENT and ANCHORAGE_FEES_RECIPIENT are the same placeholder address.

Both constants are set to 0x2002…02, so the incentives transfer (Line 84) and the anchorage-fees transfer (Line 88) currently go to the same recipient. This is incorrect production behavior and it also breaks test_ETHEREUM_sparkTreasury_transfers in Spell_20260716.t.sol: with both recipients equal, the final balance becomes INCENTIVES_AMOUNT + ANCHORAGE_FEES_AMOUNT, so the first assertEq(..., incentivesRecipientBalanceBefore + INCENTIVES_AMOUNT) will fail. Set distinct real addresses before merge.

🤖 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 `@src/proposals/20260716/SparkEthereum_20260716.sol` around lines 26 - 27,
`INCENTIVES_RECIPIENT` and `ANCHORAGE_FEES_RECIPIENT` are both using the same
placeholder value, so the transfers in the proposal currently route to one
address and break the treasury test. Update the constants in
`SparkEthereum_20260716` to use two distinct, real recipient addresses before
merge, and verify the balance expectations in
`test_ETHEREUM_sparkTreasury_transfers` still match the separate incentive and
anchorage-fees transfers.

40-41: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# How prior spells wire foreign PAYLOAD_* immutables in their constructors
rg -nP 'PAYLOAD_(ROBINHOOD|XLAYER|ARBITRUM|BASE)\s*=' src -C2

Repository: sparkdotfi/spark-spells

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files matching SparkEthereum_20260716 ==\n'
fd -a 'SparkEthereum_20260716.sol' src || true

printf '\n== Search PAYLOAD_ROBINHOOD / PAYLOAD_XLAYER declarations and usages ==\n'
rg -n --hidden -S 'PAYLOAD_(ROBINHOOD|XLAYER)\b' src -C 3 || true

printf '\n== Search for constructor assignments to payload-like immutables ==\n'
rg -n --hidden -S 'constructor\s*\(|immutable|PAYLOAD_' src/proposals src -C 2 || true

Repository: sparkdotfi/spark-spells

Length of output: 18276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Outline proposal file ==\n'
ast-grep outline src/proposals/20260716/SparkEthereum_20260716.sol --view expanded || true

printf '\n== Proposal file with line numbers ==\n'
cat -n src/proposals/20260716/SparkEthereum_20260716.sol | sed -n '1,220p'

printf '\n== Find SparkPayloadEthereum definition ==\n'
rg -n --hidden -S 'contract\s+SparkPayloadEthereum|PAYLOAD_ROBINHOOD|PAYLOAD_XLAYER|PAYLOAD_' src -C 4 || true

Repository: sparkdotfi/spark-spells

Length of output: 14258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== SparkPayloadEthereum constructor/initializer slice ==\n'
cat -n src/SparkPayloadEthereum.sol | sed -n '1,220p'

printf '\n== AaveV3PayloadBase constructor slice ==\n'
cat -n src/AaveV3PayloadBase.sol | sed -n '1,140p'

printf '\n== Search for inline initialization of PAYLOAD_* in src ==\n'
rg -n --hidden -S 'PAYLOAD_(ARBITRUM|BASE|GNOSIS|OPTIMISM|UNICHAIN|AVALANCHE|ROBINHOOD|XLAYER)\s*(=|:)' src -C 2 || true

Repository: sparkdotfi/spark-spells

Length of output: 17382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== SparkPayloadEthereum constructor/initializer slice ==\n'
sed -n '1,220p' src/SparkPayloadEthereum.sol | cat -n

printf '\n== AaveV3PayloadBase constructor slice ==\n'
sed -n '1,180p' src/AaveV3PayloadBase.sol | cat -n

printf '\n== Search for inline initialization of PAYLOAD_* in src ==\n'
rg -n --hidden -S 'PAYLOAD_(ARBITRUM|BASE|GNOSIS|OPTIMISM|UNICHAIN|AVALANCHE|ROBINHOOD|XLAYER)\s*(=|:)' src -C 2 || true

Repository: sparkdotfi/spark-spells

Length of output: 18767


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Constructors in proposal contracts ==\n'
rg -n --hidden -S 'constructor\s*\(' src/proposals -C 2 || true

printf '\n== Proposal contracts that mention PAYLOAD_ ==\n'
rg -n --hidden -S 'PAYLOAD_' src/proposals -C 2 || true

Repository: sparkdotfi/spark-spells

Length of output: 2764


Wire PAYLOAD_ROBINHOOD / PAYLOAD_XLAYER in the constructor
SparkPayloadEthereum.execute() only queues these branches when the inherited immutables are nonzero, but this contract never assigns them. Pass the Robinhood/XLayer payload addresses into the constructor and set those fields, or the new dispatches will stay disabled.

🤖 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 `@src/proposals/20260716/SparkEthereum_20260716.sol` around lines 40 - 41, The
constructor in SparkEthereum_20260716 currently leaves the inherited payload
addresses unset, so the PAYLOAD_ROBINHOOD and PAYLOAD_XLAYER branches in
SparkPayloadEthereum.execute() never activate. Update the constructor to accept
the Robinhood and XLayer payload addresses and assign them to the corresponding
inherited immutables/fields so those dispatch paths are enabled. Make sure the
existing execute() checks will now see nonzero values and queue both payloads
correctly.
src/proposals/20260716/XLayer_20260716.t.sol (1)

99-99: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Comments reference USDG/SPUSDG but this suite exercises the USDT (spUSDT) vault.

The vault under test is USDT-based (asset() == USDT, name() == "Spark Savings USDT", Lines 125-127), yet several comments were carried over from the Robinhood/USDG tests: Line 99 ("Spark Savings USDG Vault roles"), Line 108, Line 215, Line 259, and Lines 312-333. Additionally, Line 215 states the vault "already has 8.01e6 of USDG", but the assertions on Lines 226-227 verify totalAssets/totalSupply == 1e6. Please correct these to avoid confusion about which asset/amount is being validated.

Also applies to: 108-108, 215-215, 259-259, 312-333

🤖 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 `@src/proposals/20260716/XLayer_20260716.t.sol` at line 99, The comments in
this test suite still refer to USDG/SPUSDG even though the exercised vault is
the USDT-based spUSDT vault, so update the affected comments in the
XLayer_20260716_t.sol scenario to match the actual asset and vault names. In
particular, revise the placeholders around the role setup, vault state, and
withdrawal sections so they reference USDT/Spark Savings USDT instead of
USDG/SPUSDG, and fix the deposit-state comment in the section around the vault
balance check to describe the 1e6 totalAssets/totalSupply assertion rather than
an 8.01e6 USDG amount.

@octane-security-app

Copy link
Copy Markdown

Overview

Vulnerabilities found: 2                                                                                

🔗 Commit Hash: 32ee908
🛡️ Octane Dashboard: All vulnerabilities

@lucas-manuel
lucas-manuel merged commit e8092ce into feat/sc-1573-spell-20260716 Jul 7, 2026
1 check passed
@lucas-manuel
lucas-manuel deleted the add-xlayer-integration branch July 7, 2026 19:30
lucas-manuel added a commit that referenced this pull request Jul 15, 2026
* initial commit

* feat: Add RH testing infra (#175)

feat: add rh test

* feat: Add XLayer infra (#176)

* add: rh tests

* add: grove transfer usds

* add: description for robinhood chain

* feat: Add xlayer integration tests (#177)

* feat: add xlayer integration tests

* feat: update to merge latest

* fix: Update e2e tests (#178)

* fix: update e2e tests

* fix: rm prompt diff

* add: xlayer tests

* add: rh tests

* fix: review

* fix: review

* add: roles to rh alm proxy freezable and tests

* fix

* fix: partial review

* add: fix approx

* fix: review

* fix: review

* add deploy

* fix: tests

---------

Co-authored-by: Lucas Manuel <lucasmanuel.tech@gmail.com>
Co-authored-by: Charan (aka TheMj0ln1r) <104960642+TheMj0ln1r@users.noreply.github.com>
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.

1 participant