Skip to content

Native XLM donations resolve to a non-canonical SAC via `accepted_assets[].issuer #6

Description

@P3az3

Problem Statement

In campaign/src/lib.rs::get_token_address_for_asset, when AssetInfo::Native is passed to donate, the look-up code does:

let xlm_code = soroban_sdk::String::from_str(env, "XLM");
campaign.accepted_assets.iter()
    .find(|a| a.asset_code == xlm_code)
    .and_then(|a| a.issuer.clone())
    .unwrap_or_else(|| panic_with_error(env, Error::AssetNotAccepted))

If a malicious creator lists a token whose asset_code == "XLM" but whose issuer is some custom SAC, native XLM donations will be silently routed to that custom SAC contract address. Donors will see stellar terms like "XLM donation" but the funds will land in an unrelated contract.

Why it matters

This is a fund-routing vulnerability. Donors will sign transactions describing XLM (via wallet metadata), the contract will accept Native, and the funds will be redirected to a token address the donor did not intend. There is no off-chain reputation indicator on the donor's wallet that would surface this discrepancy. The corresponding claim_refund path is similarly affected: for native donors, the refund loop skips None issuers, leaving those donors unable to refund at all.

Technical Context

The same accepted_assets list drives both the donation route and the refund loop. In claim_refund the loop iterates only on assets where asset.issuer == Some(addr), ignoring Native-side donors entirely. Combined with the forger-friendly get_token_address_for_asset, an attacker can construct a campaign whose outbound funding path lies but whose inbound refund path silently drops victims.

Expected Outcome

AssetInfo::Native must route to the canonical wrapped-XLM SAC for the target network — i.e. the network's well-known native token contract address — regardless of what accepted_assets contains. The campaign should not be configurable in a way that overrides this.

Acceptance Criteria

  • get_token_address_for_asset for AssetInfo::Native returns a network-fixed, well-known wrapped XLM contract address (configurable per network in Env setup or stored as a constant). It must not consult accepted_assets.
  • A new typed error Error::NativeAssetConfigurationMismatch = 81 is returned when the canonical wrapped XLM address is not in accepted_assets (or the campaign-wide accepted_assets is reshaped to require a native-XLM entry with the canonical contract).
  • Negative tests:
    • Create a campaign with accepted_assets whose "XLM" entry's issuer points at a malicious contract → donate(Native,...) should fail/reject.
    • claim_refund for a donor who only donated Native must succeed and route to the canonical SAC.
    • Two-asset campaign (canonical XLM + custom SAC) refund must split correctly across both.

Implementation Notes

  • Introduce a pub const NATIVE_XLM_CONTRACT_TESTNET: Address and NATIVE_XLM_CONTRACT_MAINNET: Address (env-instantiated) — Soroban's well-known network addresses. Environment resolution can be done in EnvironmentConfig in crates/tools.
  • Update get_token_address_for_asset to short-circuit on Native and return the network-fixed address. Add an assert!(campaign.accepted_assets.iter().any(|a| a.asset_code == "XLM" && a.issuer == Some(NATIVE_XLM_CONTRACT))); so creator setup cannot accidentally exclude the network's XLM.
  • Update claim_refund to materialize the canonical XLM address from the same source and include it in the per-asset refund loop.
  • Add a risk_indicator helper that returns a warning string for any campaign whose accepted_assets entry has asset_code == "XLM" but mismatched issuer — for off-chain indexer use.

Affected Files / Modules

  • campaign/src/lib.rs (donate + claim_refund paths)
  • campaign/src/types.rs (StellarAsset, new constant)
  • campaign/src/test/negative_path_tests.rs (new tests)
  • crates/tools/src/environment_config.rs (network-native XLM discovery)

Dependencies — Should coordinate with Issue 9 (typed-error space expansion) and Issue 16 (refund path correctness).

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26fund-routingToken-routing or transfer-flow risk.priority/p0Highest priority. Drop everything else.securitySecurity-impact change.smart-contractSoroban/Stellar contract logic.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions