Skip to content

feat(v11): add elys xrp withdrawal#147

Merged
AdriaCarrera merged 10 commits into
xrplevm:mainfrom
aluque-peersyst:feat/add-elys-icb-blocked-xrp-withdrawal
Jun 23, 2026
Merged

feat(v11): add elys xrp withdrawal#147
AdriaCarrera merged 10 commits into
xrplevm:mainfrom
aluque-peersyst:feat/add-elys-icb-blocked-xrp-withdrawal

Conversation

@aluque-peersyst

@aluque-peersyst aluque-peersyst commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Add xrp withdrawal from elys channel as part of the v11 handler

Summary by CodeRabbit

Release Notes

  • New Features

    • The v11 network upgrade now includes automated recovery of “Elys escrow” tokens. During the upgrade, any escrowed tokens held for configured channels can be unescrowed and sent to the configured withdrawal destination, using network-specific recovery settings.
  • Bug Fixes

    • Improved handling of the v11 recovery logic so it only runs when a valid recovery configuration (including a positive amount and valid destination) is present.
  • Tests

    • Updated validator staking test mocks to match current staking keeper expectations.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

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

Walkthrough

The v11 upgrade handler is extended to drain stranded funds from the Elys IBC transfer channel escrow account. New constants for mainnet, testnet, and devnet (ElysChannelID, WithdrawalAddress, chain IDs) are defined and wired into an ElysRecoveryByNetwork map. Narrow BankKeeper and TransferKeeper keeper interfaces are introduced. A withdrawElysEscrow function conditionally releases escrowed funds: it looks up recovery config by chainID, derives the escrow address, parses the bech32 withdrawal destination, constructs a single coin, and calls UnescrowCoin. The app wires the two new keepers into CreateUpgradeHandler. An unrelated POA test mock expectation is updated to use GetUnbondingDelegations.

Changes

v11 Elys escrow withdrawal

Layer / File(s) Summary
Constants and keeper interfaces
app/upgrades/v11/constants.go, app/upgrades/v11/keepers.go
Exports UpgradeName, chain IDs for mainnet/testnet/devnet, ElysChannelID and WithdrawalAddress constants, and internal amount helpers. Introduces ElysRecovery struct (channel ID, withdrawal address, amount) and ElysRecoveryByNetwork map wiring each chain to its recovery parameters. Adds narrow BankKeeper (GetBalance) and TransferKeeper (UnescrowCoin) interfaces.
withdrawElysEscrow implementation and handler wiring
app/upgrades/v11/upgrades.go, app/upgrades.go
Extends CreateUpgradeHandler signature to accept bankKeeper and transferKeeper, calls withdrawElysEscrow after disabling ICA host (aborting upgrade on error), and implements the helper: looks up recovery config by chainID (skips if missing/empty), skips if configured amount is non-positive, derives escrow address via GetEscrowAddress, parses bech32 withdrawal destination, constructs a single coin, releases via UnescrowCoin, and logs outcome. App-level call site passes app.BankKeeper and app.TransferKeeper.

POA test mock fix

Layer / File(s) Summary
POA staking mock expectation update
x/poa/keeper/msg_server_add_validator_test.go
Replaces GetUnbondingDelegationsFromValidator mock expectation with GetUnbondingDelegations in the "should pass" test case.

Sequence Diagram(s)

sequenceDiagram
  participant UpgradeHandler
  participant withdrawElysEscrow
  participant TransferKeeper

  UpgradeHandler->>withdrawElysEscrow: call(ctx, logger, bankKeeper, transferKeeper)
  withdrawElysEscrow->>withdrawElysEscrow: lookup ElysRecoveryByNetwork[chainID]
  alt no recovery config for chainID
    withdrawElysEscrow-->>UpgradeHandler: return nil (skip)
  else recovery configured and amount positive
    withdrawElysEscrow->>withdrawElysEscrow: GetEscrowAddress(channelID)
    withdrawElysEscrow->>withdrawElysEscrow: bech32.DecodeAndConvert(WithdrawalAddress)
    withdrawElysEscrow->>TransferKeeper: UnescrowCoin(escrowAddr, withdrawalAddr, coin)
    withdrawElysEscrow-->>UpgradeHandler: log result, return nil/error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • xrplevm/node#127: Introduced the v11 upgrade handler infrastructure (ICA host disabling) that this PR extends with escrow withdrawal logic.
  • xrplevm/node#145: Also modifies CreateUpgradeHandler and setupUpgradeHandlers for v11 upgrade wiring, sharing the same handler execution path.

Suggested reviewers

  • AdriaCarrera

🐇 A bunny hopped through the IBC lane,
Found coins in escrow, stuck in the chain.
With UnescrowCoin and bech32 decode,
The stranded EVM tokens found their way home.
No more forgotten funds left behind! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and lacks required sections (Motivation, Changes with details, Considerations, Dependencies) specified in the repository template. Expand the description to follow the template structure: add Motivation section explaining why this withdrawal is needed, detailed Changes section listing each modification, Considerations section for any impacts, and Dependencies section if applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding Elys XRP withdrawal functionality as part of the v11 upgrade handler.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 2

🤖 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 `@app/upgrades/v11/constants.go`:
- Around line 10-12: The WithdrawalAddress constant in constants.go has a TODO
comment indicating it is not finalized, but this address is actively used in
upgrades.go to transfer the full escrow balance during upgrade execution. This
creates a critical risk of permanently sending funds to the wrong account.
Verify and finalize the correct withdrawal address value, replace the current
address with the verified correct address, and remove the TODO comment to
indicate that the value has been confirmed and is ready for production use.

In `@app/upgrades/v11/upgrades.go`:
- Around line 73-74: The subtraction operation in
transferKeeper.SetTotalEscrowForDenom(ctx, totalEscrow.Sub(escrowBalance)) lacks
validation and will panic if escrowBalance exceeds totalEscrow due to
inconsistent transfer accounting. Add a bounds check before line 74 to validate
that escrowBalance is less than or equal to totalEscrow retrieved on line 73. If
the check fails, log an appropriate error message and return early from the
function rather than proceeding with the subtraction that would cause a panic
during the upgrade.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: dbf78936-c778-48f0-a14d-62eed2699428

📥 Commits

Reviewing files that changed from the base of the PR and between 2d77d09 and a27f281.

📒 Files selected for processing (4)
  • app/upgrades.go
  • app/upgrades/v11/constants.go
  • app/upgrades/v11/keepers.go
  • app/upgrades/v11/upgrades.go

Comment thread app/upgrades/v11/constants.go Outdated
Comment thread app/upgrades/v11/upgrades.go Outdated
Comment thread app/upgrades/v11/constants.go Outdated
ElysChannelID = "channel-1"

// ElysEscrowAddress is the Elys channel's escrow account, pinned as a guard.
ElysEscrowAddress = "ethm1kq2rzz6fq2q7fsu75a9g7cpzjeanmk68nttack"

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.

Set different address for devnet, testnet, and mainnet.

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.

No need to store ElysEscrowAddress if we have ElysChannelID

Comment thread app/upgrades/v11/constants.go Outdated
const (
UpgradeName = "v11.0.0"
UpgradeName = "v11.0.0"
ElysChannelID = "channel-1"

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.

Set different channel id for devnet, testnet and mainnet

Comment thread app/upgrades/v11/upgrades.go Outdated
// escrow to the recovery address and updates the total-escrow accounting.
func withdrawElysEscrow(ctx sdk.Context, logger log.Logger, bankKeeper BankKeeper, transferKeeper TransferKeeper) error {
elysEscrowAddr := transfertypes.GetEscrowAddress(transfertypes.PortID, ElysChannelID)
if elysEscrowAddr.String() != ElysEscrowAddress {

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.

This validation is not needed

Comment thread app/upgrades/v11/upgrades.go Outdated

// XRP base denom from the canonical EVM coin config.
xrpDenom := evmtypes.GetEVMCoinDenom()
elysEscrowBalance := bankKeeper.GetBalance(ctx, elysEscrowAddr, xrpDenom)

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.

Use GetBalances instead of only withdrawing xrp

Comment thread app/upgrades/v11/upgrades.go Outdated
return fmt.Errorf("failed to withdraw elys escrow: %w", err)
}

totalEscrow := transferKeeper.GetTotalEscrowForDenom(ctx, xrpDenom)

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.

Attach a report explaning why we need to SetTotalEscrow

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.

The transfer module keeps a per-denom counter TotalEscrowForDenom that must equal the real balance in its escrow accounts. EscrowCoin and UnescrowCoin always move the bank balance and this counter together.

We withdraw the Elys channel with a raw SendCoins, which doesn't touch the counter. So we decrement it ourselves, or it permanently over-reports withdrawn escrow, breaking the invariant and corrupting exported genesis (GetAllTotalEscrowed). We subtract only the withdrawn amount (the same denom may be escrowed on other live channels), which also keeps it non-negative so SetTotalEscrowForDenom won't panic.

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.

Use UnescrowCoin function then

aluque-peersyst and others added 6 commits June 19, 2026 16:26
…bonding lookup

PR xrplevm#126 (fix/poa-unbonding-delegation-check) switched the AddValidator
eligibility check to k.sk.GetUnbondingDelegations(ctx, accAddress, ...) and
updated the interface, mock and keeper_test.go, but missed this msg_server
test, which still mocked GetUnbondingDelegationsFromValidator. The stale
expectation makes the real call unexpected, failing make test-poa and
make coverage-unit on the PR-merge build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve v11 upgrade conflicts by unioning both features: main's approved
staking unbonding-period change (1 week) plus this branch's elys escrow
withdrawal. The local WIP unbonding duplicate (30d) was dropped in favor
of main's version (stashed, not committed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Take origin's elys implementation (UnescrowCoin / IterateTokensInEscrow,
per-network recovery) and main's 1-week unbonding. The only delta over
origin is the POA add-validator test mock fix (GetUnbondingDelegations),
which origin was still missing.

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

@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: 1

🤖 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 `@app/upgrades/v11/upgrades.go`:
- Around line 78-93: The code is calling UnescrowCoin within the
IterateTokensInEscrow callback, which violates the Cosmos SDK contract that
prohibits writes to the same state domain while an iterator is open over it.
This can cause panics or halt the chain. Instead, collect the coins to be
unescrow during the iteration by storing them in a slice or similar data
structure within the callback (returning false to continue iteration without
calling UnescrowCoin), then after the IterateTokensInEscrow iterator closes,
iterate over the collected coins and call UnescrowCoin for each one to safely
perform the escrow state modifications.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8eab54a8-12ec-4577-9ec6-e27c1888062c

📥 Commits

Reviewing files that changed from the base of the PR and between d91d002 and c454506.

📒 Files selected for processing (4)
  • app/upgrades.go
  • app/upgrades/v11/keepers.go
  • app/upgrades/v11/upgrades.go
  • x/poa/keeper/msg_server_add_validator_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/upgrades.go

Comment thread app/upgrades/v11/upgrades.go Outdated

@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: 2

🤖 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 `@app/upgrades/v11/constants.go`:
- Around line 16-21: TestnetWithdrawalAddress and DevnetWithdrawalAddress
constants are currently set to the same value, which may indicate a copy-paste
error given that different networks should typically have distinct withdrawal
addresses for fund routing purposes. Review the intended configuration for the
devnet environment and either update DevnetWithdrawalAddress with the correct
distinct address for the devnet network, or document and confirm with the team
that sharing the same withdrawal address between testnet and devnet is
intentional.

In `@app/upgrades/v11/upgrades.go`:
- Around line 62-85: The function `withdrawElysEscrow` passes `bankKeeper` as a
parameter but never uses it, and lacks validation to safely cap the withdrawal
amount before calling `transferKeeper.UnescrowCoin`. Either remove the unused
`bankKeeper` parameter if the hardcoded `recoveryCfg.Amount` is confirmed to
exactly match the on-chain Elys channel escrow balance, or introduce a safety
cap by using `bankKeeper.GetBalance` to retrieve the actual escrow balance and
apply the minimum of that value and `recoveryCfg.Amount` before the
`UnescrowCoin` call to prevent errors or panics. Additionally, verify that
`evmtypes.GetEVMCoinDenom()` returns the exact denomination held in the Elys
channel escrow account, and confirm the hardcoded mainnet amount matches the
actual stranded balance at the upgrade height.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68ba1876-5f9d-4555-8356-544029fae7c2

📥 Commits

Reviewing files that changed from the base of the PR and between c454506 and 928a00b.

📒 Files selected for processing (3)
  • app/upgrades/v11/constants.go
  • app/upgrades/v11/keepers.go
  • app/upgrades/v11/upgrades.go
💤 Files with no reviewable changes (1)
  • app/upgrades/v11/keepers.go

Comment thread app/upgrades/v11/constants.go Outdated
Comment on lines +16 to +21
TestnetWithdrawalAddress = "ethm1p95fctckyrxuxu6t47e2uuckjl9tfuxynuawsc"

// devnet
DevnetChainID = "xrplevm_1449900-1"
DevnetElysChannelID = "channel-4"
DevnetWithdrawalAddress = "ethm1p95fctckyrxuxu6t47e2uuckjl9tfuxynuawsc"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Testnet and devnet share the same WithdrawalAddress.

TestnetWithdrawalAddress (Line 16) and DevnetWithdrawalAddress (Line 21) are identical (ethm1p95fctckyrxuxu6t47e2uuckjl9tfuxynuawsc), while a previous review asked for distinct per-network addresses. Please confirm this reuse is intentional and not a copy-paste leftover, since funds are routed irreversibly to this address during the upgrade.

🤖 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 `@app/upgrades/v11/constants.go` around lines 16 - 21, TestnetWithdrawalAddress
and DevnetWithdrawalAddress constants are currently set to the same value, which
may indicate a copy-paste error given that different networks should typically
have distinct withdrawal addresses for fund routing purposes. Review the
intended configuration for the devnet environment and either update
DevnetWithdrawalAddress with the correct distinct address for the devnet
network, or document and confirm with the team that sharing the same withdrawal
address between testnet and devnet is intentional.

Comment on lines +62 to +85
func withdrawElysEscrow(ctx sdk.Context, logger log.Logger, bankKeeper BankKeeper, transferKeeper TransferKeeper) error {
recoveryCfg, ok := ElysRecoveryByNetwork[ctx.ChainID()]
if !ok || recoveryCfg.ChannelID == "" {
logger.Info("no Elys escrow recovery configured for this network, skipping", "chainID", ctx.ChainID())
return nil
}

if !recoveryCfg.Amount.IsPositive() {
logger.Info("Elys escrow recovery amount is zero, nothing to withdraw", "chainID", ctx.ChainID())
return nil
}

escrowAddr := transfertypes.GetEscrowAddress(transfertypes.PortID, recoveryCfg.ChannelID)

destAddr, err := sdk.AccAddressFromBech32(recoveryCfg.WithdrawalAddress)
if err != nil {
return fmt.Errorf("invalid withdrawal address %q: %w", recoveryCfg.WithdrawalAddress, err)
}

coin := sdk.NewCoin(evmtypes.GetEVMCoinDenom(), recoveryCfg.Amount)

if err := transferKeeper.UnescrowCoin(ctx, escrowAddr, destAddr, coin); err != nil {
return fmt.Errorf("failed to unescrow %s from elys escrow: %w", coin, err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In cosmos/ibc-go v10.4.0, does TransferKeeper.UnescrowCoin return an error or panic when the coin amount exceeds the escrow account balance or the TotalEscrowForDenom counter?

💡 Result:

In ibc-go v10.4.0, the TransferKeeper.UnescrowCoin function does not explicitly check if the coin amount exceeds the escrow account balance or the TotalEscrowForDenom counter; instead, it relies on the underlying BankKeeper and Cosmos SDK arithmetic to handle these operations, which leads to different outcomes [1][2]. 1. Escrow Account Balance: The function calls k.BankKeeper.SendCoins to move tokens from the escrow address to the receiver [1][2]. If the escrow account has insufficient balance to cover the requested amount, the BankKeeper.SendCoins method will return an error [1]. In this case, UnescrowCoin wraps and returns that error, rather than panicking [1][2]. 2. TotalEscrowForDenom Counter: After sending the coins, the function calls currentTotalEscrow.Sub(coin) [1][2]. If the coin amount exceeds the current total escrow stored in the state, the Cosmos SDK's Int.Sub method (or similar Sub operation on the Coin type) will typically panic if it results in a negative amount [3]. While the code tracks escrow, it assumes the operation is valid based on the successful bank transfer; if the escrow state becomes inconsistent (e.g., if more tokens are unescrowed than were ever escrowed due to a malicious counterparty), this subtraction operation will trigger a panic [1][2]. The documentation explicitly notes that if a negative amount is ever attempted to be stored, the keeper function will panic [3]. Therefore, while the function handles bank-level insufficient funds via error propagation, an inconsistency between the actual escrowed tokens and the tracked TotalEscrowForDenom state will lead to a panic during the subtraction step [1][3].

Citations:


🏁 Script executed:

# First, let's look at the full function and surrounding context
cat -n app/upgrades/v11/upgrades.go | head -100

Repository: xrplevm/node

Length of output: 3734


🏁 Script executed:

# Search for ElysRecoveryByNetwork definition and bankKeeper usage
rg "ElysRecoveryByNetwork|BankKeeper" app/upgrades/v11/ -A 3 -B 1

Repository: xrplevm/node

Length of output: 2047


🏁 Script executed:

# Search for GetEVMCoinDenom implementation
rg "GetEVMCoinDenom" --type go -A 5

Repository: xrplevm/node

Length of output: 150


🏁 Script executed:

# Search for GetEVMCoinDenom - it's imported from evmtypes
rg "func.*GetEVMCoinDenom" --type go -A 5

Repository: xrplevm/node

Length of output: 150


🏁 Script executed:

# Look at the constants file to see ElysRecoveryByNetwork and related values
cat -n app/upgrades/v11/constants.go

Repository: xrplevm/node

Length of output: 2372


🏁 Script executed:

# Also check if there are any other usages of bankKeeper in the upgrade file
rg "bankKeeper\." app/upgrades/v11/

Repository: xrplevm/node

Length of output: 150


🌐 Web query:

cosmos/evm GetEVMCoinDenom what denom does it return

💡 Result:

In the Cosmos EVM module, the function GetEVMCoinDenom() returns the base denomination (a string) used as the native EVM token for the chain [1][2]. This value represents the specific token denomination from the bank module that is used for EVM state transitions, gas consumption, and other native EVM operations [3][4]. By default, this is typically set to "uatom" [5][3], but it is a configurable parameter that must match the chain's native staking and minting denominations [3][6]. It is important to distinguish this from other denominations used in the module: - GetEVMCoinDenom: Returns the base denomination (e.g., "uatom") [1][2]. - GetEVMCoinExtendedDenom: Returns the extended denomination used for 18-decimal representation if configured [2]. - GetEVMCoinDisplayDenom: Returns the human-readable display denomination (e.g., "atom") [2].

Citations:


🏁 Script executed:

# Check if there are any comments or documentation about the denom in this file
rg "denom|Denom|DENOM" app/upgrades/v11/

Repository: xrplevm/node

Length of output: 353


Remove unused bankKeeper parameter or apply balance safety cap before UnescrowCoin.

The function passes bankKeeper but never invokes GetBalance to cap the withdrawal. With a hardcoded recoveryCfg.Amount, the code relies on that value exactly matching the on-chain stranded balance:

  • If recoveryCfg.Amount exceeds the escrow account's balance for the denom, UnescrowCoin returns an error and the upgrade halts.
  • If recoveryCfg.Amount exceeds the tracked TotalEscrowForDenom, the subtraction panics during the upgrade.
  • If recoveryCfg.Amount is less than the actual stranded balance, funds remain stranded.

For mainnet, mainnetElysAmount is hardcoded to "6955539034646993768414" with a comment claiming it is the "exact XRP stranded." Confirm this value precisely matches the on-chain Elys channel escrow balance at upgrade height. If the hardcoded amount is intentional, remove the now-dead bankKeeper parameter and its interface wiring. Otherwise, re-introduce bankKeeper.GetBalance to safely cap the amount.

Also verify that evmtypes.GetEVMCoinDenom() returns the exact denomination held in the Elys channel escrow account, otherwise UnescrowCoin will operate on the wrong denom.

🤖 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 `@app/upgrades/v11/upgrades.go` around lines 62 - 85, The function
`withdrawElysEscrow` passes `bankKeeper` as a parameter but never uses it, and
lacks validation to safely cap the withdrawal amount before calling
`transferKeeper.UnescrowCoin`. Either remove the unused `bankKeeper` parameter
if the hardcoded `recoveryCfg.Amount` is confirmed to exactly match the on-chain
Elys channel escrow balance, or introduce a safety cap by using
`bankKeeper.GetBalance` to retrieve the actual escrow balance and apply the
minimum of that value and `recoveryCfg.Amount` before the `UnescrowCoin` call to
prevent errors or panics. Additionally, verify that `evmtypes.GetEVMCoinDenom()`
returns the exact denomination held in the Elys channel escrow account, and
confirm the hardcoded mainnet amount matches the actual stranded balance at the
upgrade height.

@AdriaCarrera AdriaCarrera merged commit 4711990 into xrplevm:main Jun 23, 2026
2 of 3 checks passed
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.

2 participants