Skip to content

fix(dataworker): skip orbit gas funding for Universal chains (Robinhood)#3526

Open
droplet-rl wants to merge 2 commits into
masterfrom
droplet/T90K0AL22-C03GHT4RV42-1782477070-619989
Open

fix(dataworker): skip orbit gas funding for Universal chains (Robinhood)#3526
droplet-rl wants to merge 2 commits into
masterfrom
droplet/T90K0AL22-C03GHT4RV42-1782477070-619989

Conversation

@droplet-rl

Copy link
Copy Markdown
Contributor

Problem

The zion-across-l1-executor (dataworker) crashed during pool-leaf execution:

TypeError: Cannot destructure property 'amountWei' of
'ARBITRUM_ORBIT_L1L2_MESSAGE_FEE_DATA[leaf.chainId]' as it is undefined.
  at Dataworker._getRequiredEthForOrbitPoolRebalanceLeaf (Dataworker.js:1978)
  at Dataworker._executePoolRebalanceLeaves (Dataworker.js:1258)

This throws out of the whole _executePoolLeavesAndSyncL1Tokens run, so it blocks all pool-rebalance-leaf execution in the bundle, not just Robinhood's leaf.

Root cause

Robinhood (CHAIN_IDs.ROBINHOOD) is technically an Arbitrum Orbit chain, so sdkUtils.chainIsOrbit() returns true for it. _executePoolRebalanceLeaves routes every chainIsArbitrum || chainIsOrbit leaf through _getRequiredEthForOrbitPoolRebalanceLeaf, which destructures ARBITRUM_ORBIT_L1L2_MESSAGE_FEE_DATA[leaf.chainId].

But Robinhood relays HubPool→SpokePool roots and tokens via the Succinct/Helios SP1 light-client bridge (HubPoolStore), not the Orbit Inbox — it's in UNIVERSAL_CHAINS. So it needs no ETH pre-loaded for L1→L2 message fees and has no entry in ARBITRUM_ORBIT_L1L2_MESSAGE_FEE_DATA (there's an existing @TODO about this in Constants.ts). The destructure hits undefined and throws.

Fix

Exclude UNIVERSAL_CHAINS from the Orbit gas-funding path in _executePoolRebalanceLeaves. This mirrors the finalizer, which already special-cases UNIVERSAL_CHAINS for the Helios L1→L2 path. Universal leaves now fall straight through to executeRootBundle with no loadEthForL2Calls funding tx.

Testing

  • yarn build
  • yarn lint / prettier ✅
  • _executePoolRebalanceLeaves suite ✅ (6 passing) — added does not fund universal orbit leaf (e.g. Robinhood) asserting a Robinhood leaf enqueues executeRootBundle with no loadEthForL2Calls; existing funds arbitrum leaf still passes, so real Arbitrum funding is unchanged.

Context: Slack thread where Paul noted "For Robinhood we are using succinct, not the orbit message bridge."

🤖 Generated with Claude Code

Robinhood is technically an Arbitrum Orbit chain (chainIsOrbit returns
true) but relays HubPool->SpokePool roots and tokens via the
Succinct/Helios SP1 light-client bridge (HubPoolStore), not the Orbit
Inbox. It therefore needs no ETH pre-loaded for L1->L2 message fees and
has no entry in ARBITRUM_ORBIT_L1L2_MESSAGE_FEE_DATA.

_executePoolRebalanceLeaves routed any chainIsArbitrum/chainIsOrbit leaf
through _getRequiredEthForOrbitPoolRebalanceLeaf, which destructured the
(undefined) fee data and threw, crashing the entire pool-leaf execution
run:

  TypeError: Cannot destructure property 'amountWei' of
  'ARBITRUM_ORBIT_L1L2_MESSAGE_FEE_DATA[leaf.chainId]' as it is undefined.

Exclude UNIVERSAL_CHAINS from the orbit gas-funding path, consistent with
the finalizer's existing UNIVERSAL_CHAINS handling. Adds a regression
test asserting a Robinhood leaf enqueues executeRootBundle with no
loadEthForL2Calls funding tx.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66d77809d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// For non-Universal orbit leaves we need to check we have enough gas tokens to pay for the
// L1 to L2 message.
if (
UNIVERSAL_CHAINS.includes(leaf.chainId) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Document the Universal-chain execution path

This changes dataworker pool-rebalance execution behavior by bypassing Orbit gas funding for Universal chains, but the repo guideline in AGENTS.md says to “Keep all relevant AGENTS.md and README.md files updated in the same change whenever behavior, configuration, interfaces, or runtime flow changes.” Please update the relevant dataworker docs (for example src/dataworker/README.md) so operators/contributors can see why Universal Orbit leaves skip loadEthForL2Calls.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — added a short "Orbit gas funding during pool-leaf execution" subsection to src/dataworker/README.md (commit a4263a3) explaining the Arbitrum/Orbit loadEthForL2Calls pre-funding step and why UNIVERSAL_CHAINS (Robinhood) skip it, mirroring the finalizer's Helios L1→L2 path.

Comment on lines +1895 to +1896
UNIVERSAL_CHAINS.includes(leaf.chainId) ||
(!sdkUtils.chainIsArbitrum(leaf.chainId) && !sdkUtils.chainIsOrbit(leaf.chainId))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve funding for Robinhood token sends

This early-continues all Robinhood leaves before the token-send fee reservation, but the current config still enables Robinhood WETH pool leaves (src/common/Constants.ts:374) and maps Robinhood WETH to ArbitrumOrbitBridge (src/common/Constants.ts:548-550), whose constructL1ToL2Txn sends an outboundTransfer with nonzero ETH value for Robinhood (src/adapter/bridges/ArbitrumOrbitBridge.ts:24-27, :92-96). In a Robinhood leaf with a positive WETH netSendAmount, this now queues only executeRootBundle; if the HubPool has no preloaded ETH for L2 calls, the token bridge leg can fail even though the root message itself uses the Universal/Helios path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, but I believe this is a false positive — it conflates two independent subsystems.

_executePoolRebalanceLeaves (this PR) only enqueues two HubPool txs: loadEthForL2Calls (pre-funding the HubPool to pay the Arbitrum Inbox L1→L2 submission fee) and executeRootBundle. The ArbitrumOrbitBridge.constructL1ToL2Txn / outboundTransfer you reference lives in the inventory-rebalancing subsystem: the CUSTOM_BRIDGE map (Constants.ts:548) is consumed only by AdapterManager, Refiller, and jussi — never by the dataworker pool-leaf path. That adapter sends outboundTransfer from the relayer's own l1Signer wallet with its own value (l1SubmitValue), which is entirely independent of the HubPool's preloaded ETH. This PR does not touch that path, so it is unchanged.

For the HubPool's own root-bundle token relay, Robinhood is a Universal chain: it relays roots and net token amounts via the Succinct/Helios SP1 bridge (HubPoolStore / Universal SpokePool), not the Orbit Inbox — so the HubPool needs no preloaded ETH. This is confirmed by the existing @TODO at ARBITRUM_ORBIT_L1L2_MESSAGE_FEE_DATA ("Do we even need this for Robinhood? We are using Universal SpokePool"), Robinhood having no fee-data entry, and the finalizer routing UNIVERSAL_CHAINS through heliosL1toL2Finalizer.

Note also that before this PR the missing fee-data entry made _getRequiredEthForOrbitPoolRebalanceLeaf throw for Robinhood, so no Robinhood leaf could execute at all — there was never a working "fund the Robinhood token send" path to regress. No code change here.

Codex review (P2) flagged that AGENTS.md asks READMEs to track runtime-flow
changes. Document why pool-leaf execution skips loadEthForL2Calls for
UNIVERSAL_CHAINS (Robinhood) in src/dataworker/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@droplet-rl

Copy link
Copy Markdown
Contributor Author

Thanks @codex for the review. Addressed both:

P2 (docs) — accepted. Added an "Orbit gas funding during pool-leaf execution" subsection to src/dataworker/README.md (commit a4263a3) documenting the Arbitrum/Orbit loadEthForL2Calls pre-funding step and why UNIVERSAL_CHAINS (Robinhood) skip it.

P1 (Robinhood WETH token sends) — respectfully disagree, no code change. This conflates two independent subsystems. _executePoolRebalanceLeaves only enqueues loadEthForL2Calls (HubPool pre-funding for the Arbitrum Inbox) + executeRootBundle. The ArbitrumOrbitBridge/outboundTransfer path you cite is the inventory-rebalancing subsystem (CUSTOM_BRIDGE → consumed only by AdapterManager/Refiller/jussi, never the dataworker), which sends from the relayer's own l1Signer wallet with its own value — independent of HubPool ETH and untouched by this PR. The HubPool's own root-bundle relay for Robinhood goes via Succinct/Helios (Universal SpokePool), not the Orbit Inbox, so it needs no preloaded ETH — confirmed by the ARBITRUM_ORBIT_L1L2_MESSAGE_FEE_DATA @TODO, the missing fee-data entry, and the finalizer's heliosL1toL2Finalizer. Full reasoning is on the inline thread.

@codex please re-review the updated diff.

@droplet-rl

Copy link
Copy Markdown
Contributor Author

🔎 View trace

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66d77809d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// For non-Universal orbit leaves we need to check we have enough gas tokens to pay for the
// L1 to L2 message.
if (
UNIVERSAL_CHAINS.includes(leaf.chainId) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update dataworker docs for the funding skip

This changes the dataworker runtime path by skipping the Orbit loadEthForL2Calls pre-funding step for Universal chains, but this commit only updates code/tests. The repo-level AGENTS.md says to “Keep all relevant AGENTS.md and README.md files updated in the same change whenever behavior, configuration, interfaces, or runtime flow changes,” so src/dataworker/README.md should describe why Robinhood/Universal leaves bypass Orbit gas funding to avoid future operators or contributors reintroducing the old pre-funding assumption.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already addressed — this review re-ran on the original commit 66d77809 before the docs landed. The follow-up commit a4263a35 adds an "Orbit gas funding during pool-leaf execution" subsection to src/dataworker/README.md explaining the Arbitrum/Orbit loadEthForL2Calls pre-funding step and why UNIVERSAL_CHAINS (Robinhood) bypass it, so future operators/contributors won't reintroduce the old pre-funding assumption.

@droplet-rl

Copy link
Copy Markdown
Contributor Author

This second Codex review re-ran on the original commit 66d77809 (before the docs landed), and its single P2 is a duplicate of the docs suggestion from the prior review — already addressed by commit a4263a35, which adds an "Orbit gas funding during pool-leaf execution" subsection to src/dataworker/README.md explaining why UNIVERSAL_CHAINS (Robinhood) bypass the Orbit loadEthForL2Calls pre-funding. Replied on the inline thread; no new code change needed and not re-tagging @codex to avoid a redundant review loop.

@droplet-rl

Copy link
Copy Markdown
Contributor Author

🔎 View trace

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

  • No new code or documentation change was needed for this follow-up. The duplicate P2 review was against the older commit 66d77809, while the current branch already contains the requested dataworker documentation in src/dataworker/README.md. The existing subsection explains the Arbitrum/Orbit loadEthForL2Calls pre-funding behavior and why UNIVERSAL_CHAINS such as Robinhood skip that step. src/dataworker/README.mdL27-L31
  • The current implementation already skips Orbit gas pre-funding for Universal chains before falling through to normal executable pool-leaf handling. src/dataworker/Dataworker.tsL1888-L1899
  • I did not create a new commit or PR because I made no changes. I also observed an existing uncommitted yarn.lock modification in the working tree, but it was unrelated to this follow-up and I left it untouched.

Testing

  • find .. -name AGENTS.md -print
  • cat AGENTS.md && git status --short && git log --oneline -5 && rg -n "Orbit gas funding during pool-leaf execution|UNIVERSAL_CHAINS|loadEthForL2Calls" src/dataworker/README.md src/dataworker/Dataworker.ts
  • nl -ba src/dataworker/README.md | sed -n '20,36p'; nl -ba src/dataworker/Dataworker.ts | sed -n '1888,1902p'

View task →

@droplet-rl

Copy link
Copy Markdown
Contributor Author

Confirmed — we're aligned. The docs requested by the duplicate P2 are already on the branch in commit a4263a35 (src/dataworker/README.md, "Orbit gas funding during pool-leaf execution"), and the Universal-chain skip in Dataworker.ts is unchanged. No further action needed; PR is ready for human review.

(Note: the stray uncommitted .env/yarn.lock-type working-tree changes are environment artifacts, not part of this PR — only the two committed changes, 66d77809 + a4263a35, reach the branch.)

@droplet-rl

Copy link
Copy Markdown
Contributor Author

🔎 View trace

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