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).
Problem Statement
In
campaign/src/lib.rs::get_token_address_for_asset, whenAssetInfo::Nativeis passed todonate, the look-up code does:If a malicious creator lists a token whose
asset_code == "XLM"but whoseissueris 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_refundpath is similarly affected: for native donors, the refund loop skipsNoneissuers, leaving those donors unable to refund at all.Technical Context
The same
accepted_assetslist drives both the donation route and the refund loop. Inclaim_refundthe loop iterates only on assets whereasset.issuer == Some(addr), ignoringNative-side donors entirely. Combined with the forger-friendlyget_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::Nativemust route to the canonical wrapped-XLM SAC for the target network — i.e. the network's well-known native token contract address — regardless of whataccepted_assetscontains. The campaign should not be configurable in a way that overrides this.Acceptance Criteria
get_token_address_for_assetforAssetInfo::Nativereturns a network-fixed, well-known wrapped XLM contract address (configurable per network inEnvsetup or stored as a constant). It must not consultaccepted_assets.Error::NativeAssetConfigurationMismatch = 81is returned when the canonical wrapped XLM address is not inaccepted_assets(or the campaign-wideaccepted_assetsis reshaped to require a native-XLM entry with the canonical contract).accepted_assetswhose "XLM" entry'sissuerpoints at a malicious contract →donate(Native,...)should fail/reject.claim_refundfor a donor who only donated Native must succeed and route to the canonical SAC.Implementation Notes
pub const NATIVE_XLM_CONTRACT_TESTNET: AddressandNATIVE_XLM_CONTRACT_MAINNET: Address(env-instantiated) — Soroban's well-known network addresses. Environment resolution can be done inEnvironmentConfigincrates/tools.get_token_address_for_assetto short-circuit onNativeand return the network-fixed address. Add anassert!(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.claim_refundto materialize the canonical XLM address from the same source and include it in the per-asset refund loop.risk_indicatorhelper that returns a warning string for any campaign whoseaccepted_assetsentry hasasset_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).