Skip to content

feat: HyperCore spot-balance withdraw leaf for counterfactual deposits - #1503

Draft
droplet-rl wants to merge 1 commit into
masterfrom
droplet/T90K0AL22-C0B8JA20R24-1784968036-530869
Draft

feat: HyperCore spot-balance withdraw leaf for counterfactual deposits#1503
droplet-rl wants to merge 1 commit into
masterfrom
droplet/T90K0AL22-C0B8JA20R24-1784968036-530869

Conversation

@droplet-rl

Copy link
Copy Markdown
Contributor

Motivation

First live case of a Core-side stranding: a user spot-Sent 3,848.240373 USDC on HyperCore to their Across v3 counterfactual deposit address 0xACDe1Bc40B4DF760FB388DA503ee6a99794E2cb6 (counterfactual_deposit_address id 1083070, Intercom 215475222911726). A HyperCore internal Send never touches an EVM chain, so no leaf in the creation-time route tree — including WithdrawImplementation — can reach the funds: the clone's Core account can only be debited by the clone itself, via a CoreWriter action.

What this adds

  • HyperCoreWithdrawImplementation — a withdraw leaf implementation (HyperEVM-only) that CoreWriter-spotSends the clone's HyperCore spot balance to the leaf-committed user. params/submitterData mirror WithdrawImplementation (WithdrawParams(admin, user) / (token, to, amount)), so both AdminWithdrawManager paths (directWithdraw, signedWithdrawToUser) work unchanged. Differences: token is the Core token's asset-bridge address (USDC = 0x2000…0000), amount is in Core wei units (u64), and the Core recipient is always the committed userto is ignored.
  • DeployHyperCoreWithdrawImplementation.s.sol — CREATE2 deploy (salt 0), mirroring the existing withdraw-impl deploy script.
  • RecoverHyperCoreSpotBalance.s.sol — worked recovery for the live incident: rebuilds the 8-leaf route tree (7 creation-time leaf hashes + the new Core-withdraw leaf, same WithdrawParams as the existing withdraw leaf), the single-leaf upgrade tree, and prints the 4-tx sequence:
    1. beacon.setUpgradeRoot(upgradeRoot) — beacon owner Safe (0xd396Cc…2A7F, 3/5 on chain 999)
    2. factory.deploy(salt, initialRoot) — anyone (materializes the proxy at the deposit address)
    3. proxy.updateRoot(newRoot, []) — anyone
    4. manager.directWithdraw(…) — directWithdrawer Safe (same Safe)
  • DESIGN.md note under Withdraw leaf.

Verification

  • 9 Foundry tests incl. an end-to-end testRecoveryFlowViaRootUpdate (root update adds the Core leaf to a clone minted without it, then the manager withdraws) and both AWM-path compatibility tests. All pass.
  • Recovery script run against live HyperEVM: factory.predictAddress(salt, initialRoot) == the deposit address; recomputed withdraw leaf == creation materials leaf hash; every DB merkle proof verifies against initialRoot.
  • Live Core state re-verified 2026-07-25: exactly 3,848.240373 USDC (total, hold 0) at the address; recipient == creation-pinned refund address == the user's main account.

Safety

The new leaf is authorized for this one proxy only (single-leaf upgrade tree), the recipient is pinned in the leaf params to the creation-time refund address, and submitterData cannot redirect funds. Worst case of a bad broadcast is a silently-failed Core action (funds stay put) — the script header documents the post-broadcast Core-side verification step.

Slack context: https://risklabs.slack.com/archives/C0B8JA20R24/p1784968036530869

🤖 Generated with Claude Code

Adds HyperCoreWithdrawImplementation, a withdraw leaf implementation that
recovers funds stranded in a counterfactual deposit address's HyperCore
spot balance (Core-side spot Sends never touch an EVM chain, so
WithdrawImplementation cannot reach them). It CoreWriter-spotSends the
clone's Core balance to the leaf-committed user; params/submitterData
mirror WithdrawImplementation so both AdminWithdrawManager paths work
unchanged, and the recipient is always the committed user.

Includes the recovery script for the live incident (deposit address
0xACDe1Bc4...2cb6, id 1083070, 3,848.240373 USDC): builds the +1-leaf
route tree and single-leaf upgrade tree and prints the four-tx sequence
(setUpgradeRoot -> factory.deploy -> updateRoot -> directWithdraw).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.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