Skip to content

Allow ERC20 Teleport on Moonbase#3758

Open
arturgontijo wants to merge 19 commits into
masterfrom
artur/teleport
Open

Allow ERC20 Teleport on Moonbase#3758
arturgontijo wants to merge 19 commits into
masterfrom
artur/teleport

Conversation

@arturgontijo

@arturgontijo arturgontijo commented May 21, 2026

Copy link
Copy Markdown
Contributor

Goal of the changes

Add a per-contract ERC-20 teleport mechanism to pallet-erc20-xcm-bridge and turn it on
for Moonbase so that whitelisted ERC-20s can be teleported to/from Asset Hub (para
1001 on Moonbase). Native assets (DEV / GLMR / MOVR) stay non-teleportable. Moonbeam
and Moonriver get the pallet wired but keep their existing IsTeleporter = () /
XcmTeleportFilter = Nothing gates, so the feature is dormant on production runtimes
until a follow-up PR flips them.

This PR also closes a high-severity split-transactor drain on deregistered
whitelisted ERC-20s: a local polkadotXcm.execute program could route WithdrawAsset
through the legacy reserve adapter (bookkeeping only) and DepositAsset through the
teleport transactor (real payout from TeleportCheckingAccount).

What reviewers need to know

Most of the diff lives in pallets/erc20-xcm-bridge plus a small number of runtime /
weight tweaks needed to make the new flow actually executable. Highlights:

Pallet (pallets/erc20-xcm-bridge/src/lib.rs)

  • Added a root-gated whitelist TeleportableErc20s: StorageMap<H160, TeleportableErc20Status>
    with a three-state lifecycle (Registered, Active, Deregistered) and a
    per-contract LockedSupply: StorageMap<H160, U256> counter that tracks the
    outstanding cross-chain obligation (i.e. how much supply this runtime currently
    holds locked in TeleportCheckingAccount for that contract).

    The counter is what makes the lifecycle robust:

    • It is maintained atomically with every teleport leg: withdraw_asset
      saturating-adds, deposit_asset saturating-subs, internal_transfer_asset is a
      no-op (same-chain hop, never moves the checking account). Promotion to Active
      happens inside the same storage layer as the EVM transfer, so promotion + counter
      • EVM-transfer commit/rollback together.
    • It replaces an EVM balanceOf call. balanceOf would tie the lifecycle to a
      contract that may revert/honeypot, drift on direct (non-XCM) transfers, and add a
      full EVM round-trip per remove call. The counter is just a Twox64Concat → U256
      storage read.
    • Drift modes are documented inline in the storage doc-comment: direct
      ERC20.transfer(checking, …) donations are intentionally ignored (they have no
      foreign-asset twin to track), and adversarial off-pallet drains conservatively
      keep the entry non-purgeable.

    Three extrinsics gated by the existing TeleportAdminOrigin config item:

    • add_teleportable_erc20(contract) → admin-only. (none) → Registered
      (fresh add) or Deregistered → Registered (revival of a previously removed
      contract; preserves any pre-existing LockedSupply). Errors with
      Erc20AlreadyTeleportable only on Registered/Active (the duplicate cases).
      Emits TeleportableErc20Added on both fresh-add and revival.
    • remove_teleportable_erc20(contract)dual-purpose, with the caller's
      required origin depending on the current LockedSupply and status:
      • LockedSupply == 0 → purge the entry outright (storage map + counter).
        Origin is admin-only when the current status is Registered (so a third
        party can't snipe a freshly added entry before the operator finishes
        configuring) and when the current status is Active (so a third party
        cannot snipe a live operational entry the moment its counter momentarily
        hits zero between in/out flows — the operator stays in control of when an
        active contract leaves the whitelist). Origin is permissionless (any
        signed origin or root) only when the current status is Deregistered:
        admin already opted into wind-down by flipping the entry, and the public
        sweep just finalizes that intent once the obligation is fully discharged.
        Emits TeleportableErc20Purged.
      • LockedSupply > 0 → admin-only, flip Registered/Active to
        Deregistered. Outbound is closed for that contract; inbound stays open from
        TeleportTrustedLocation so users can keep teleporting their twin home and
        decrementing the counter. Already-Deregistered returns Erc20AlreadyRemoved.
        Emits TeleportableErc20Removed.
      • Unknown contract → Erc20NotTeleportable.
    • force_remove_teleportable_erc20(contract)admin escape hatch. Deletes the
      entry and its counter regardless of state and counter value, emitting
      TeleportableErc20ForceRemoved { contract, status_before, locked_supply } so
      the act is auditable from chain events. Use only when the operator is willing
      to forfeit any outstanding obligation (e.g. compromised contract).

    Events: TeleportableErc20Added, TeleportableErc20Activated,
    TeleportableErc20Removed, TeleportableErc20Purged, TeleportableErc20ForceRemoved.
    Errors: Erc20AlreadyTeleportable, Erc20NotTeleportable, Erc20AlreadyRemoved.

  • Added a new Erc20TeleportTransactor<T> TransactAsset impl that performs real EVM
    transfers
    against T::TeleportCheckingAccount and maintains the counter +
    status promotion atomically:

    • withdraw_asset (outbound) → ERC20.transfer(user → checking), then
      LockedSupply += amount, then Registered → Active if applicable.
      Outbound-eligible only (Registered or Active).
    • deposit_asset (inbound) → ERC20.transfer(checking → beneficiary), then
      LockedSupply -=(saturating) amount, then Registered → Active if applicable.
      Admits any whitelisted state (Registered, Active, Deregistered) so removed
      contracts can still unwind supply home.
    • internal_transfer_assetERC20.transfer(from → to) (same-chain). Promotes
      Registered → Active (it's a successful flow handled by this transactor) but
      does NOT touch LockedSupply (no checking-account interaction). Outbound-eligible
      only.
    • can_check_in / can_check_out enforce both the asset state AND the
      counterparty location (T::TeleportTrustedLocation).
    • Outbound routing (match_outbound).
      • Unknown (not whitelisted) ERC-20 multilocation → XcmError::AssetNotFound so
        the AssetTransactors tuple falls through to the legacy Erc20XcmBridge
        adapter (reserve-mode single-EVM-call optimisation).
      • DeregisteredXcmError::FailedToTransactAsset (no fallthrough). This
        blocks fresh outbound locks and prevents the legacy adapter from handling the
        withdraw leg of a split program.
    • Zero-amount fast path. All three legs short-circuit on Fungible(0)
      after the whitelist match: no EVM call, no LockedSupply write, no
      Registered → Active promotion, no Activated event. Adversaries can still
      deliver Fungible(0) via SCALE-decoded XCM (the upstream
      Fungibility::from(u128) debug_assert_ne! only protects local typed
      construction), so without this gate they could spam-promote whitelisted
      contracts and burn EVM gas on no-op transfers. Non-whitelisted contracts with
      Fungible(0) still surface AssetNotFound so the legacy reserve adapter can
      take over.
  • Legacy adapter fence (legacy_transactor_rejects_teleportable). Every
    Erc20XcmBridge TransactAsset leg (withdraw_asset, deposit_asset,
    internal_transfer_asset) returns AssetNotFound for any contract present in
    TeleportableErc20s (all lifecycle states). Together with the teleport
    transactor's FailedToTransactAsset on Deregistered outbound, this prevents a
    single XCM execution from splitting withdraw and deposit across two adapters —
    the audited drain on deregistered contracts.

  • Deposit-side invariant (documented in code). deposit_asset pays from
    TeleportCheckingAccount without re-validating origin on Deregistered entries.
    That is sound only because the executor gates every holding-fill path upstream
    (WithdrawAsset with real EVM debit + legacy fence, ReceiveTeleportedAsset /
    IsTeleporter + can_check_in, ReserveAssetDeposited / IsReserve). Reviewers
    should treat any future loosening of those gates as a checking-account drain risk.

  • IsTeleportableErc20<T> exposes both ContainsPair<Asset, Location> (for
    xcm_executor::Config::IsTeleporter) and Contains<(Location, Vec<Asset>)> (for
    pallet_xcm::Config::XcmTeleportFilter). Native SelfReserve cannot match the ERC-20
    prefix, so it is structurally excluded from teleport.

    • The ContainsPair impl ANDs the whitelist (any state — Registered, Active,
      or Deregistered) with a fixed counterparty T::TeleportTrustedLocation.
      Without that bind, any sibling parachain or the relay able to deliver an XCM
      could present a whitelisted ERC-20 in ReceiveTeleportedAsset, pass
      IsTeleporter, and drain TeleportCheckingAccount via the follow-up
      DepositAsset. Each runtime points it at its AssetHubLocation. Admitting
      Deregistered here is deliberate — it's what keeps the teleport-back path
      alive after remove.
    • The Contains<(Location, Vec<Asset>)> impl admits ONLY outbound-eligible states
      (Registered, Active) so pallet_xcm::limited_teleport_assets cannot start
      a fresh outbound teleport for a deregistered contract. It stays asset-only
      because pallet_xcm::limited_teleport_assets calls it with the local caller's
      origin (not the destination) before the executor runs. The destination is gated
      by IsTeleporter::contains(asset, dest) in pallet_xcm::teleport_assets_program,
      which runs before any local WithdrawAsset instruction is built — so a
      non-AH destination errors with Filtered and the user's ERC-20s never reach the
      EVM checking account.
    • For defense-in-depth, Erc20TeleportTransactor::can_check_in and can_check_out
      also verify the counterparty location matches T::TeleportTrustedLocation; if a
      future change loosens IsTeleporter, the transactor still refuses.
  • Pallet Config now requires TeleportAdminOrigin, TeleportCheckingAccount, and
    TeleportTrustedLocation.

Moonbase wiring (runtime/moonbase/src/{lib.rs,xcm_config.rs})

  • construct_runtime! upgrades Erc20XcmBridge from {Pallet} to
    {Pallet, Call, Storage, Event<T>} so the new admin extrinsics and events are
    dispatchable.
  • New parameter_types! { Erc20TeleportCheckingAccount: H160 = H160(*b"erc20-teleport-check"); }
    — a fixed 20-byte payload chosen so the address has no preimage from any standard
    derivation (no parachain sovereign, no AccountId32 alias).
  • pallet_erc20_xcm_bridge::Config adds TeleportAdminOrigin = EnsureRoot<AccountId>,
    TeleportCheckingAccount = Erc20TeleportCheckingAccount, and
    TeleportTrustedLocation = AssetHubLocation (para 1001).
  • AssetTransactors now includes pallet_erc20_xcm_bridge::Erc20TeleportTransactor<Runtime>
    before the legacy Erc20XcmBridge. Whitelisted contracts use the lock/unlock
    transactor; everything else still uses the original single-EVM-call reserve
    optimisation.
  • Gates flipped:
    XcmExecutorConfig::IsTeleporter = pallet_erc20_xcm_bridge::IsTeleportableErc20<Runtime>
    and pallet_xcm::Config::XcmTeleportFilter = IsTeleportableErc20<Runtime>.
  • Benchmark hook (XcmPalletTeleportBenchmark) inserts the fixture as Registered
    so the user-facing XcmTeleportFilter admits it; the benchmark exercises the full
    outbound path which would auto-promote to Active in real flows.

Plumbing fixes that make teleport actually executable on Moonbase

  • runtime/moonbase/src/weights/xcm/mod.rsreceive_teleported_asset flipped from
    Weight::MAX to a real measurable weight (reused from reserve_asset_deposited),
    otherwise inbound teleport messages get bounced as Unsupported by messageQueue.
  • runtime/moonbase/src/weights/pallet_xcm.rsteleport_assets no longer returns
    Weight::MAX from a Benchmark::Override placeholder. It now aliases
    Self::transfer_assets(), since the user-facing limited_teleport_assets ends up doing
    comparable work (EVM withdraw + HRMP send). Comment added explaining the rationale.
  • runtime/common/src/xcm_pallet_benchmark.rs (new) and the accompanying
    runtime/common/src/{lib.rs,apis.rs} edits — introduces an XcmPalletTeleportBenchmark
    per-runtime trait so Moonbase can override teleportable_asset_and_dest for
    benchmarking without forcing Moonbeam/Moonriver to do the same. Default None keeps
    upstream behaviour for the dormant runtimes.

Moonbeam / Moonriver (runtime/{moonbeam,moonriver}/src/{lib.rs,xcm_config.rs})

  • construct_runtime! exposes Call, Storage, Event<T> for the pallet so the workspace
    builds.
  • Pallet Config is wired with the same TeleportAdminOrigin /
    TeleportCheckingAccount / TeleportTrustedLocation.
  • IsTeleporter and XcmTeleportFilter are intentionally left at () and Nothing.
    The whitelist storage exists but no teleport flow is reachable on these runtimes until a
    follow-up PR flips both gates and applies the same weight fixes.

Tests

  • pallets/erc20-xcm-bridge/src/tests.rs31 unit tests covering:

    • Lifecycle: fresh add → Registered; duplicate add rejected on Registered/Active;
      revival from Deregistered preserves LockedSupply; admin-origin enforcement.
    • First-leg auto-promotion: withdraw_asset, deposit_asset, and
      internal_transfer_asset all flip Registered → Active exactly once and emit
      TeleportableErc20Activated only on the actual transition.
    • Counter tracking: withdraw_asset accumulates; deposit_asset saturating-subs
      (handles pre-seeded twin supply by clamping to zero rather than aborting);
      internal_transfer_asset does not move the counter.
    • remove matrix: rejects unknown; Registered + count == 0 admin-only purge;
      Active + count == 0 admin-only purge (live entries cannot be sniped on a
      transient-zero counter); Deregistered + count == 0 permissionless purge
      (any signed origin or root) — the only state from which this is allowed;
      Active + count > 0 admin-only flip to Deregistered; Deregistered + count > 0 returns Erc20AlreadyRemoved.
    • End-to-end happy path: add → outbound × 2 → admin remove (Deregistered) →
      inbound drains counter to 0 → permissionless purge → re-add succeeds.
    • force_remove: admin-only; rejects unknown; purges any state and counter;
      audit event records status_before and locked_supply; post-force_remove
      inbound is refused (entry is gone).
    • Public helpers (is_teleportable_erc20, is_outbound_eligible_erc20),
      IsTeleporter location-bind, XcmTeleportFilter outbound-eligible-only with
      empty/mixed-list defenses.
    • Split-transactor / routing regressions:
      outbound_transactor_rejects_deregistered_and_unknown (Deregistered
      FailedToTransactAsset, unknown → AssetNotFound);
      deregistered_split_transactor_withdraw_deposit_cannot_drain_checking_account;
      legacy_transactor_rejects_all_teleportable_erc20_states.
    • Zero-amount fast path: all three legs no-op on Fungible(0) for whitelisted
      contracts (counter unchanged, status stays Registered, no Activated
      event), and non-whitelisted contracts with Fungible(0) still surface
      AssetNotFound.
  • runtime/moonbase/tests/erc20_teleport.rs11 integration tests on the actual
    moonbase-runtime:

    • Filter gates: native DEV rejected; non-whitelisted ERC-20 rejected; whitelisted
      passes both filter forms; untrusted locations rejected on IsTeleporter.
    • Lifecycle / admin: full whitelist extrinsic matrix via EnsureRoot;
      force_remove_teleportable_erc20 escape hatch;
      deregistered_contract_keeps_inbound_open_and_blocks_outbound_via_runtime_filters.
    • User extrinsics: limited_teleport_assets rejects non-AH destinations;
      limited_reserve_transfer_assets to AH returns Filtered for whitelisted ERC-20;
      non-AH reserve transfer does not strand funds in the checking account.
    • deregistered_withdraw_deposit_program_cannot_drain_checking_account — full
      XcmExecutor::prepare_and_execute with [WithdrawAsset, DepositAsset] on a
      deregistered contract (tuple-level regression for the audited drain).
  • test/suites/dev/moonbase/test-xcm-teleport/test-xcm-teleport-erc20.ts — Moonwall
    suite D024501 (foundationMethods: dev, three cases):

    • T01 — deploy + whitelist + limited_teleport_assets to mock AH (para 1001);
      asserts Alith balance ↓, checking ↑, LockedSupply ↑, status Active.
    • T02 — mock HRMP inbound ReceiveTeleportedAsset + DepositAsset (DEV fees
      withdrawn from AH sovereign; native cannot ride ReceiveTeleportedAsset); unwinds
      locked supply to Charleth.
    • T03 — single end-to-end deregistered drain regression on a separate
      DrainCoin contract: deploy → whitelist → seed checking → set LockedSupply
      deregister → Charleth polkadotXcm.execute([WithdrawAsset, BuyExecution, DepositAsset]) must fail with balances and LockedSupply unchanged.

Risks / compatibility

  • Native assets stay non-teleportable. GLMR/MOVR cannot match the ERC-20 prefix, so
    they cannot pass IsTeleportableErc20. This is asserted by the
    teleport_filter_rejects_native_dev integration test.
  • Production runtimes are unchanged behaviourally. On Moonbeam/Moonriver,
    IsTeleporter and XcmTeleportFilter are unchanged, so
    pallet_xcm::limited_teleport_assets is still Filtered. The only observable surface
    change on production is the addition of three root-only / permissionless-purge admin
    extrinsics (no-op until the gates flip).
  • Deregistered contracts: inbound open, outbound closed, no split-transactor drain.
    After remove_teleportable_erc20 with LockedSupply > 0, users can still teleport
    home from AH, but fresh outbound teleports and local polkadotXcm.execute withdraw/deposit
    programs cannot extract checking-account supply via the legacy + teleport adapter split.
  • Permissionless purge is gated on Deregistered. remove_teleportable_erc20
    accepts any signed origin (or root) only when LockedSupply == 0 and the
    status is Deregistered. This is intentional: by deregistering the entry the
    admin has explicitly opted into wind-down, and once the obligation is fully
    discharged the public sweep just finalizes that intent — giving a deregistered
    contract a natural terminus without requiring operator intervention. Active + count == 0 is admin-only, deliberately: a live operational contract
    routinely transits through count == 0 between in/out flows, so admitting a
    third-party purge there would let anyone snipe an active entry the moment its
    counter momentarily hits zero, forcing the admin to re-add. Registered + count == 0 is admin-only too so a third party can't undo a fresh add. For
    defense in depth there's also force_remove_teleportable_erc20 (admin-only)
    to delete any entry regardless of state.
  • Moonbase reserve flows for whitelisted ERC-20s now use 2 EVM calls instead of 1.
    Because Erc20TeleportTransactor does the lock in withdraw_asset and the unlock in
    deposit_asset (no in-message tracker), an MB→non-AH reserve transfer of a whitelisted
    contract uses the checking address as an intermediate hop. End state is identical to
    today; gas cost is doubled for that specific path. Non-whitelisted ERC-20s still use the
    legacy single-call reserve optimisation untouched.
  • Counterpart trust on AH is required at runtime. Even with this PR merged, no
    teleport will succeed until Asset Hub registers the ERC-20 foreign-asset twin and calls
    pallet_assets::set_reserves(asset, [{ reserve: (1, Parachain(<MB para>)), teleportable: true }]).
    The local-zombienet helper script we use for smoke testing handles AH-Westend (para
    1001); production AH-Polkadot needs governance to do the same calls.
  • Storage migrations: none. The new TeleportableErc20s map and LockedSupply
    counter both start empty. The pallet index for Erc20XcmBridge is preserved on all
    three runtimes (48 / 110 / 110). Adding Call/Storage/Event to
    construct_runtime! widens the metadata but does not move state.

Testing

  • cargo check -p pallet-erc20-xcm-bridge -p moonbase-runtime -p moonbeam-runtime -p moonriver-runtime
    clean.
  • cargo test -p pallet-erc20-xcm-bridge31/31 pass (see breakdown above).
  • cargo test --test erc20_teleport -p moonbase-runtime11/11 pass (see breakdown above).
  • pnpm moonwall test dev_moonbase D0245013/3 pass (dev-node HRMP mock + EVM regressions).
  • cargo check -p moonbase-runtime --features runtime-benchmarks clean (with
    WASM_BUILD_WORKSPACE_HINT=$PWD).
  • ReadLints clean on all touched files.
  • Local zombienet smoke-test can be run by:
cargo b -r

cd /tmp
git clone -b teleport https://github.com/arturgontijo/runtime-upgrade-local.git
cd runtime-upgrade-local

npm i

# Copy the moonbeam binary to ./zombienet/bin

# 1. Zombienet + HRMP (separate terminals or sequential)
npm run zombienet:setup
npm run zombienet:spawn:ah-moonbase
npm run zombienet:open-hrmp

# 2. Compile ERC20WithInitialSupply, deploy on Moonbase, register on both chains
npm run zombienet:teleport:setup

# 3. Moonbase → AH (signer Alith, credits //Bob on AH by default)
npm run zombienet:teleport:send -- moonbase-to-ah 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3 1000000000

# 4. AH → Moonbase (default limitedTeleportAssets + Unlimited remote weight)
npm run zombienet:teleport:send -- ah-to-moonbase 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3 500000000

# 5. Alith EVM balance on Moonbase
npm run zombienet:teleport:balances -- 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3 Alith

# 6. //Bob foreign asset balance on AH
npm run zombienet:teleport:balances -- 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3 //Bob

Intentionally not in this PR yet: flipping IsTeleporter / XcmTeleportFilter on
Moonbeam (and Moonriver).

@arturgontijo arturgontijo self-assigned this May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 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

Adds a whitelist-driven ERC-20 teleport transactor and filter, on-chain whitelist lifecycle and counters, admin extrinsics for whitelist management, runtime wiring across Moonbase/Moonbeam/Moonriver, benchmark hook delegation, and extensive unit/integration tests for lifecycle and gate behavior.

Changes

ERC-20 Teleport Feature

Layer / File(s) Summary
Pallet docs, Config, storage & governance
pallets/erc20-xcm-bridge/src/lib.rs
Module docs updated; Config gains TeleportAdminOrigin, TeleportCheckingAccount, and TeleportTrustedLocation; TeleportableErc20Status, TeleportableErc20s, and LockedSupply added; events/errors and admin extrinsics plus public query helpers implemented.
Teleport transactor implementation
pallets/erc20-xcm-bridge/src/lib.rs
Erc20TeleportTransactor implements TransactAsset for whitelisted teleports (enforces trusted location, routes via TeleportCheckingAccount, updates LockedSupply and lifecycle); zero-amount fast-path and legacy-adapter behavior included.
Teleport filter
pallets/erc20-xcm-bridge/src/lib.rs
IsTeleportableErc20 implements ContainsPair<Asset,Location> and Contains<(Location,Vec<Asset>)> to bind trust to TeleportTrustedLocation and restrict outbound assets to Registered/Active.
Mock runtime wiring & constants
pallets/erc20-xcm-bridge/src/mock.rs
Mock runtime exposes Call/Storage/Event; configures EVM test gas/ratios; defines Erc20MultilocationPrefix, TeleportCheckingAccount, TeleportTrustedLocation; provides H160 conversion helper and wires TeleportAdminOrigin to EnsureRoot.
Pallet unit tests
pallets/erc20-xcm-bridge/src/tests.rs
Adds extensive unit tests and helpers covering whitelist lifecycle, withdraw/deposit/internal transfer behavior, locked-supply counter semantics, zero-amount fast-paths, force-remove/purge flows, and transactor/filter matcher behavior.
Benchmarking infrastructure
runtime/common/src/lib.rs, runtime/common/src/xcm_pallet_benchmark.rs, runtime/common/src/apis.rs
Adds feature-gated XcmPalletTeleportBenchmark trait with default teleportable_asset_and_dest(); pallet_xcm_benchmarks delegates to runtime hook.
Moonbase XCM integration & tests
runtime/moonbase/src/lib.rs, runtime/moonbase/src/xcm_config.rs, runtime/moonbase/tests/erc20_teleport.rs
AssetTransactors includes Erc20TeleportTransactor; IsTeleporter/XcmTeleportFilter set to IsTeleportableErc20<Runtime>; adds Erc20TeleportCheckingAccount, refines AssetFeesFilter, seeds whitelist for benchmark, and adds integration tests for filters and admin semantics.
Runtime wiring (Moonbeam / Moonriver)
runtime/moonbeam/src/lib.rs, runtime/moonbeam/src/xcm_config.rs, runtime/moonriver/src/lib.rs, runtime/moonriver/src/xcm_config.rs
construct_runtime! variants updated to export Call, Storage, Event<T> for Erc20XcmBridge; each runtime’s pallet_erc20_xcm_bridge::Config adds teleport-associated types and provides benchmark-only impls when gated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

B10-Bridge, I4-tests 🎯

Suggested reviewers

  • manuelmauro
  • librelois
  • RomarQ
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Allow ERC20 Teleport on Moonbase' directly and clearly summarizes the primary change: enabling ERC20 teleport functionality specifically on the Moonbase runtime.
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.
Description check ✅ Passed The PR description comprehensively describes the changeset, covering pallet changes, runtime wiring, test coverage, and risk analysis.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch artur/teleport

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 and usage tips.

@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 (3)
runtime/moonbase/tests/erc20_teleport.rs (1)

148-156: 💤 Low value

Misleading "idempotent-safe" comment.

The behavior asserted here is the opposite of idempotent: a second add_teleportable_erc20 call returns Erc20AlreadyTeleportable rather than succeeding as a no-op. Consider rewording to avoid confusion for future readers.

✏️ Suggested wording
-			// Root works and is idempotent-safe (duplicate add fails).
+			// Root works; duplicate add is rejected (not idempotent).
🤖 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 `@runtime/moonbase/tests/erc20_teleport.rs` around lines 148 - 156, Update the
misleading test comment: replace "Root works and is idempotent-safe (duplicate
add fails)." with a clear description that the first call to
Erc20XcmBridge::add_teleportable_erc20 with root_origin() succeeds and a
duplicate call returns
pallet_erc20_xcm_bridge::Error::<Runtime>::Erc20AlreadyTeleportable (i.e., the
operation is not idempotent and duplicate adds error rather than being a no-op).
pallets/erc20-xcm-bridge/src/lib.rs (2)

138-139: 💤 Low value

Hardcoded extrinsic weights are not benchmarked.

Both add_teleportable_erc20 and remove_teleportable_erc20 use a hand-rolled Weight::from_parts(15_000_000, 0).saturating_add(T::DbWeight::get().reads_writes(1, 1)). Since these are root-gated, the practical risk is low, but a generated WeightInfo would keep them aligned with the runtime's benchmarking conventions and let weights drift with hardware/runtime updates. Consider adding a WeightInfo associated type on Config and benchmarking these two calls (matches the pattern used elsewhere in this repo for governance-only extrinsics).

Also applies to: 158-159

🤖 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 `@pallets/erc20-xcm-bridge/src/lib.rs` around lines 138 - 139, The hardcoded
weights on the extrinsics add_teleportable_erc20 and remove_teleportable_erc20
should be replaced with benchmark-generated weights: add a WeightInfo associated
type to the pallet Config (e.g. type WeightInfo: WeightInfo;), implement a
WeightInfo trait with methods add_teleportable_erc20() and
remove_teleportable_erc20() (matching the repo's existing pattern), update the
#[pallet::weight(...)] attributes to call
T::WeightInfo::add_teleportable_erc20() and
T::WeightInfo::remove_teleportable_erc20() respectively (keeping the
T::DbWeight::get().reads_writes(...) additions if needed), and run the
benchmarking harness to generate the concrete weights and wire them into the
runtime.

419-458: 💤 Low value

Consider adding direct unit tests for the TransactAsset impl.

The current tests.rs exercises IsTeleportableErc20 and the governance extrinsics, but the actual withdraw_asset / deposit_asset / internal_transfer_asset EVM paths on Erc20TeleportTransactor aren't covered by unit tests in this pallet. Given these methods are the load-bearing piece of the teleport semantics (single-EVM-call vs two-call lock/unlock, error propagation via with_storage_layer, rejection of non-whitelisted assets with AssetNotHandled so the legacy adapter takes over), at minimum it'd be worth a test that:

  • deploys a tiny ERC-20 in the EVM mock, whitelists it, and checks withdraw_asset ends up with the balance on TeleportCheckingAccount;
  • verifies a non-whitelisted asset returns the fall-through error so the tuple-impl can defer to Pallet<T>.

Not strictly required to merge — Moonbase integration tests partially cover the happy path — but it would localize regressions to this pallet instead of the runtime test suite.

🤖 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 `@pallets/erc20-xcm-bridge/src/lib.rs` around lines 419 - 458, Add unit tests
that directly exercise the TransactAsset implementation
(Erc20TeleportTransactor) by: (1) deploying a minimal ERC-20 in the EVM mock,
calling the whitelist logic (IsTeleportableErc20) and invoking withdraw_asset to
assert the ERC-20 transfer path (Pallet::<T>::erc20_transfer) moves tokens to
TeleportCheckingAccount and respects gas_limit_of_erc20_transfer; and (2)
calling withdraw_asset/internal_transfer_asset with a non-whitelisted Asset to
assert it returns AssetNotHandled (so the legacy adapter can fall through).
Locate tests around the TransactAsset impl and use
AccountIdConverter::convert_location, match_whitelisted, and
frame_support::storage::with_storage_layer to reproduce the runtime behavior and
error propagation. Ensure assertions cover both success (balance moved) and
fall-through error cases.
🤖 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 `@pallets/erc20-xcm-bridge/src/lib.rs`:
- Around line 362-377: The doc comment for Erc20TeleportTransactor incorrectly
claims a `mint_asset(asset)` override exists; update the comment to remove or
correct that line and state that `mint_asset` is not implemented (defaults to
XcmError::Unimplemented) or that minting is handled via the
`ReceiveTeleportedAsset` flow using `can_check_in`/`check_in` + `DepositAsset` →
`deposit_asset`; reference Erc20TeleportTransactor, TransactAsset, mint_asset,
deposit_asset, ReceiveTeleportedAsset, can_check_in and check_in to clarify the
actual flow.

In `@runtime/common/src/apis.rs`:
- Around line 960-962: Add no-op impls of the XcmPalletTeleportBenchmark trait
for every runtime that can be built with the runtime-benchmarks feature so the
UFCS call in runtime/common/src/apis.rs compiles; specifically, add empty impl
blocks like impl
moonbeam_runtime_common::xcm_pallet_benchmark::XcmPalletTeleportBenchmark for
Runtime {} (and the analogous impl for Moonriver) in each runtime's xcm
configuration module (e.g., the same files that contain Xcm configuration such
as xcm_config.rs) so that the call to <Runtime as
XcmPalletTeleportBenchmark>::teleportable_asset_and_dest() resolves.

In `@runtime/moonbase/src/xcm_config.rs`:
- Around line 770-782: The current check accepts any local multilocation that
starts with the ERC-20 bridge pallet prefix; tighten AssetFeesFilter by also
verifying the interior sequence is exactly [PalletInstance(idx),
AccountKey20(..)]: when location.parents == 0 and the first interior matches
Junction::PalletInstance(idx) and
Erc20XcmBridgePalletLocation::get().first_interior() yields a
PalletInstance(prefix_idx) with idx == prefix_idx, additionally ensure the
second interior junction exists and matches Junction::AccountKey20(_) (or the
exact AccountKey20 shape used in the code) before returning true so only the
precise ERC-20 teleport asset shape is accepted.

---

Nitpick comments:
In `@pallets/erc20-xcm-bridge/src/lib.rs`:
- Around line 138-139: The hardcoded weights on the extrinsics
add_teleportable_erc20 and remove_teleportable_erc20 should be replaced with
benchmark-generated weights: add a WeightInfo associated type to the pallet
Config (e.g. type WeightInfo: WeightInfo;), implement a WeightInfo trait with
methods add_teleportable_erc20() and remove_teleportable_erc20() (matching the
repo's existing pattern), update the #[pallet::weight(...)] attributes to call
T::WeightInfo::add_teleportable_erc20() and
T::WeightInfo::remove_teleportable_erc20() respectively (keeping the
T::DbWeight::get().reads_writes(...) additions if needed), and run the
benchmarking harness to generate the concrete weights and wire them into the
runtime.
- Around line 419-458: Add unit tests that directly exercise the TransactAsset
implementation (Erc20TeleportTransactor) by: (1) deploying a minimal ERC-20 in
the EVM mock, calling the whitelist logic (IsTeleportableErc20) and invoking
withdraw_asset to assert the ERC-20 transfer path (Pallet::<T>::erc20_transfer)
moves tokens to TeleportCheckingAccount and respects
gas_limit_of_erc20_transfer; and (2) calling
withdraw_asset/internal_transfer_asset with a non-whitelisted Asset to assert it
returns AssetNotHandled (so the legacy adapter can fall through). Locate tests
around the TransactAsset impl and use AccountIdConverter::convert_location,
match_whitelisted, and frame_support::storage::with_storage_layer to reproduce
the runtime behavior and error propagation. Ensure assertions cover both success
(balance moved) and fall-through error cases.

In `@runtime/moonbase/tests/erc20_teleport.rs`:
- Around line 148-156: Update the misleading test comment: replace "Root works
and is idempotent-safe (duplicate add fails)." with a clear description that the
first call to Erc20XcmBridge::add_teleportable_erc20 with root_origin() succeeds
and a duplicate call returns
pallet_erc20_xcm_bridge::Error::<Runtime>::Erc20AlreadyTeleportable (i.e., the
operation is not idempotent and duplicate adds error rather than being a no-op).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bea0c577-96f7-4303-bebd-6d12ea4f064f

📥 Commits

Reviewing files that changed from the base of the PR and between 8847817 and b26c547.

⛔ Files ignored due to path filters (2)
  • runtime/moonbase/src/weights/pallet_xcm.rs is excluded by !**/weights/**/*.rs
  • runtime/moonbase/src/weights/xcm/mod.rs is excluded by !**/weights/**/*.rs
📒 Files selected for processing (13)
  • pallets/erc20-xcm-bridge/src/lib.rs
  • pallets/erc20-xcm-bridge/src/mock.rs
  • pallets/erc20-xcm-bridge/src/tests.rs
  • runtime/common/src/apis.rs
  • runtime/common/src/lib.rs
  • runtime/common/src/xcm_pallet_benchmark.rs
  • runtime/moonbase/src/lib.rs
  • runtime/moonbase/src/xcm_config.rs
  • runtime/moonbase/tests/erc20_teleport.rs
  • runtime/moonbeam/src/lib.rs
  • runtime/moonbeam/src/xcm_config.rs
  • runtime/moonriver/src/lib.rs
  • runtime/moonriver/src/xcm_config.rs

Comment thread pallets/erc20-xcm-bridge/src/lib.rs Outdated
Comment thread runtime/common/src/apis.rs Outdated
Comment thread runtime/moonbase/src/xcm_config.rs
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 21, 2026
@arturgontijo

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@arturgontijo arturgontijo changed the title Allow Teleport on Moonbase Allow ERC20 Teleport on Moonbase May 22, 2026
@arturgontijo arturgontijo added B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes D9-needsaudit👮 PR contains changes to fund-managing logic that should be properly reviewed and externally audited not-breaking Does not need to be mentioned in breaking changes labels May 22, 2026

@librelois librelois left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Findings

  • High: inbound teleports can unlock whitelisted ERC-20s from any XCM origin, not just AssetHub.
    runtime/moonbase/src/xcm_config.rs:285 wires IsTeleporter = IsTeleportableErc20<Runtime>, but pallets/erc20-xcm-bridge/src/lib.rs:479 ignores the Location argument and only checks that the asset contract is whitelisted. On inbound ReceiveTeleportedAsset, XCM executor uses that location as the teleport origin; accepting any origin means any chain able to deliver XCM to Moonbase can present a whitelisted ERC-20 teleport and trigger deposit_asset, which transfers real ERC-20s out of TeleportCheckingAccount at pallets/erc20-xcm-bridge/src/lib.rs:403. The filter needs to bind the asset to the trusted counterparty, e.g. AssetHub, for inbound trust.

  • Medium: outbound user teleports are not restricted to AssetHub.
    runtime/moonbase/src/xcm_config.rs:347 uses the same IsTeleportableErc20<Runtime> as XcmTeleportFilter, and pallets/erc20-xcm-bridge/src/lib.rs:485 ignores the Location in (Location, Vec<Asset>). That lets whitelisted ERC-20 teleport calls pass for arbitrary destinations, despite the change request being specifically “to/from AssetHub”. At best this can lock user ERC-20s into the checking account and send messages to chains that reject or mishandle them.

  • Medium: removing a whitelist entry can strand already locked funds.
    The docs say removed assets “must still be claimed via the inbound teleport path” at pallets/erc20-xcm-bridge/src/lib.rs:154, but remove_teleportable_erc20 deletes the only bit that Erc20TeleportTransactor::match_whitelisted requires at pallets/erc20-xcm-bridge/src/lib.rs:381. After removal, inbound unlocks for that contract return AssetNotHandled, so any in-flight or legitimately returning balances in TeleportCheckingAccount become inaccessible via the new path unless root re-adds the contract.

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 22, 2026

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

🧹 Nitpick comments (1)
runtime/moonbase/src/xcm_config.rs (1)

870-878: ⚡ Quick win

Reset benchmark LockedSupply for deterministic setup.

The benchmark helper uses a fixed contract; explicitly clearing LockedSupply here prevents stale state from influencing repeated benchmark setup paths.

Suggested change
 		let contract = H160([0x42; 20]);
 		pallet_erc20_xcm_bridge::TeleportableErc20s::<Runtime>::insert(
 			contract,
 			pallet_erc20_xcm_bridge::TeleportableErc20Status::Registered,
 		);
+		pallet_erc20_xcm_bridge::LockedSupply::<Runtime>::remove(&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 `@runtime/moonbase/src/xcm_config.rs` around lines 870 - 878, The benchmark
setup uses a fixed `contract` and inserts into
`pallet_erc20_xcm_bridge::TeleportableErc20s`, but it doesn't clear
`pallet_erc20_xcm_bridge::LockedSupply`, which can leave stale state across
runs; update the setup to explicitly remove or reset
`pallet_erc20_xcm_bridge::LockedSupply` for that `contract` (or clear the entire
map) before inserting into `TeleportableErc20s` and setting
`TeleportableErc20Status::Registered`, ensuring deterministic benchmark state
for the helper that uses `contract`.
🤖 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 `@runtime/moonbase/src/xcm_config.rs`:
- Around line 870-878: The benchmark setup uses a fixed `contract` and inserts
into `pallet_erc20_xcm_bridge::TeleportableErc20s`, but it doesn't clear
`pallet_erc20_xcm_bridge::LockedSupply`, which can leave stale state across
runs; update the setup to explicitly remove or reset
`pallet_erc20_xcm_bridge::LockedSupply` for that `contract` (or clear the entire
map) before inserting into `TeleportableErc20s` and setting
`TeleportableErc20Status::Registered`, ensuring deterministic benchmark state
for the helper that uses `contract`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6dc5676d-5a0d-4aaf-beba-c036ba7002aa

📥 Commits

Reviewing files that changed from the base of the PR and between 8e8bc16 and 22ba1c0.

📒 Files selected for processing (5)
  • pallets/erc20-xcm-bridge/src/lib.rs
  • pallets/erc20-xcm-bridge/src/mock.rs
  • pallets/erc20-xcm-bridge/src/tests.rs
  • runtime/moonbase/src/xcm_config.rs
  • runtime/moonbase/tests/erc20_teleport.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 22, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 22, 2026
@arturgontijo

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

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

🧹 Nitpick comments (2)
pallets/erc20-xcm-bridge/src/tests.rs (1)

81-84: ⚡ Quick win

Use the configured trusted-location constant instead of duplicating it in the helper.

This keeps tests aligned with mock runtime config and avoids drift if the configured location changes.

Suggested diff
 fn trusted_loc() -> Location {
-	Location::new(1, [Junction::Parachain(1001)])
+	crate::mock::TeleportTrustedLocation::get()
 }
🤖 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 `@pallets/erc20-xcm-bridge/src/tests.rs` around lines 81 - 84, The helper
function trusted_loc() duplicates the test's trusted location; update it to use
the configured mock constant instead of hardcoding the Location so tests stay in
sync with the runtime config—replace the body of trusted_loc() to return the
mock runtime's trusted-location constant (e.g.
mock::TeleportTrustedLocation::get() or the appropriate TeleportTrustedLocation
constant) so the helper references the single source of truth used by the tests.
runtime/moonbase/src/xcm_config.rs (1)

881-885: 💤 Low value

Silently discarding append_with failure could cause benchmark to produce invalid asset location.

Location::append_with can fail if the interior is already at maximum capacity (8 junctions). While Erc20XcmBridgePalletLocation only has 1 junction (PalletInstance), adding AccountKey20 should succeed. However, defensive handling would be cleaner.

♻️ Suggested defensive handling
 		let mut asset_location = Erc20XcmBridgePalletLocation::get();
-		let _ = asset_location.append_with(AccountKey20 {
+		asset_location.append_with(AccountKey20 {
 			key: contract.0,
 			network: None,
-		});
+		}).expect("ERC-20 location append must succeed in benchmark");
🤖 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 `@runtime/moonbase/src/xcm_config.rs` around lines 881 - 885, The code
currently ignores the Result from asset_location.append_with(...) which can
fail; change the call to handle errors from
Erc20XcmBridgePalletLocation::get().append_with(AccountKey20 { key: contract.0,
network: None }) by checking the Result returned by append_with on the variable
asset_location and failing loudly (e.g., use expect with a clear message or
propagate an error) instead of discarding it so a failed append doesn't produce
an invalid asset location; reference the symbols asset_location, append_with,
AccountKey20, contract.0 and update the call site to handle the Err case
explicitly.
🤖 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 `@pallets/erc20-xcm-bridge/src/tests.rs`:
- Around line 81-84: The helper function trusted_loc() duplicates the test's
trusted location; update it to use the configured mock constant instead of
hardcoding the Location so tests stay in sync with the runtime config—replace
the body of trusted_loc() to return the mock runtime's trusted-location constant
(e.g. mock::TeleportTrustedLocation::get() or the appropriate
TeleportTrustedLocation constant) so the helper references the single source of
truth used by the tests.

In `@runtime/moonbase/src/xcm_config.rs`:
- Around line 881-885: The code currently ignores the Result from
asset_location.append_with(...) which can fail; change the call to handle errors
from Erc20XcmBridgePalletLocation::get().append_with(AccountKey20 { key:
contract.0, network: None }) by checking the Result returned by append_with on
the variable asset_location and failing loudly (e.g., use expect with a clear
message or propagate an error) instead of discarding it so a failed append
doesn't produce an invalid asset location; reference the symbols asset_location,
append_with, AccountKey20, contract.0 and update the call site to handle the Err
case explicitly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c558faf9-9113-42d7-a0ce-6f38b433eff6

📥 Commits

Reviewing files that changed from the base of the PR and between 5461d2c and e8d732f.

⛔ Files ignored due to path filters (2)
  • runtime/moonbase/src/weights/pallet_xcm.rs is excluded by !**/weights/**/*.rs
  • runtime/moonbase/src/weights/xcm/mod.rs is excluded by !**/weights/**/*.rs
📒 Files selected for processing (13)
  • pallets/erc20-xcm-bridge/src/lib.rs
  • pallets/erc20-xcm-bridge/src/mock.rs
  • pallets/erc20-xcm-bridge/src/tests.rs
  • runtime/common/src/apis.rs
  • runtime/common/src/lib.rs
  • runtime/common/src/xcm_pallet_benchmark.rs
  • runtime/moonbase/src/lib.rs
  • runtime/moonbase/src/xcm_config.rs
  • runtime/moonbase/tests/erc20_teleport.rs
  • runtime/moonbeam/src/lib.rs
  • runtime/moonbeam/src/xcm_config.rs
  • runtime/moonriver/src/lib.rs
  • runtime/moonriver/src/xcm_config.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 23, 2026
@arturgontijo

Copy link
Copy Markdown
Contributor Author

@librelois last commits addressed your points, so this PR is ready for another review.

One edge case that the current counter logic (LockedSupply) will not work well is if a foreign asset is registered as isSufficient: true, that way the sum will not be zero, eg:

  • Alice teleports 100 from Moonbase to AH (LockedSupply: 100)
  • AH debits 5 as fee
  • Alice teleports all her funds back (AH -> Moonbase).
  • Moonbase just receive 95 (LockedSupply: 5)

But even in this scenarion Moonbase is able to purge the token contract storage data by calling force_remove_teleportable_erc20()

@librelois librelois left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Medium finding: whitelisting an ERC-20 changes all XCM TransactAsset handling for that contract on Moonbase, not only the limited_teleport_assets path.

Because Erc20TeleportTransactor is placed before the legacy Erc20XcmBridge adapter, Registered / Active contracts are handled by the teleport transactor first. That is intentional-looking from the comment, but it means ops should treat the whitelist as “AH teleport-only / changed XCM semantics for this ERC-20,” not as a narrow teleport flag.

What to change:

  • Add a Moonbase integration test for whitelisted ERC-20 + limited_reserve_transfer_assets.
  • Assert the expected behavior explicitly, likely Filtered when the destination is AssetHubLocation because pallet-xcm classifies it as teleportable and rejects reserve-transfer for teleport assets.
  • Add/adjust a short code comment near AssetTransactors or the whitelist extrinsic docs saying that whitelisting changes generic XCM handling for that ERC-20, so only tokens intended for the AH teleport path should be whitelisted.

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

Labels

B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes D9-needsaudit👮 PR contains changes to fund-managing logic that should be properly reviewed and externally audited not-breaking Does not need to be mentioned in breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants