Skip to content

fix(globe-wallet): use case-insensitive matching for native XLM checks (closes #86) - #112

Open
rupesh-kumar-sah wants to merge 1 commit into
Orbit-Wal:mainfrom
rupesh-kumar-sah:fix/native-xlm-case-insensitive-equality
Open

rupesh-kumar-sah wants to merge 1 commit into
Orbit-Wal:mainfrom
rupesh-kumar-sah:fix/native-xlm-case-insensitive-equality

Conversation

@rupesh-kumar-sah

Copy link
Copy Markdown

Issue

Closes #86

Root cause

In GlobeWallet::add_asset (contracts/globe-wallet/src/lib.rs), asset code duplicate detection was historically updated to use case-insensitive equality (codes_match_case_insensitive), but the native asset check remained strict case-sensitive equality:

let is_native = asset.code == String::from_str(&env, "XLM");

Because of this mismatch:

  1. Registering a case-variant like "xlm" with an issuer bypassed is_native (since "xlm" != "XLM"), fell to the non-native branch where issuer.is_some() was satisfied, and successfully registered as a valid asset.
  2. Later, when the user or application tried to register the real native XLM (code: "XLM", issuer: None), the duplicate detection loop compared "XLM" against the existing "xlm" case-insensitively, detected a match, and rejected the registration with AssetAlreadyAdded.
  3. The real native XLM was permanently locked out from the wallet.

Decision on case-variants with issuer:
Any case-variant of "XLM" (e.g. "xlm", "Xlm", "xLm") that provides an issuer is rejected outright with WalletError::InvalidAssetInfo. There is no legitimate use case on Stellar for an issued asset masquerading under any case-variant of the native token symbol, and treating all case-variants consistently ensures that no squatted entry can bypass native checks.

What changed and why

Updated the is_native check in GlobeWallet::add_asset to use the canonical case-insensitive helper:

let is_native = Self::codes_match_case_insensitive(&asset.code, &String::from_str(&env, "XLM"));

Now, any asset code matching "XLM" (case-insensitive) that provides an issuer is rejected with WalletError::InvalidAssetInfo. Real native XLM ("XLM" with issuer: None) continues to register cleanly on the happy path.

Definition of done — addressed item by item

  • is_native check uses the same canonicalization as the duplicate-detection loop: Changed asset.code == String::from_str(&env, "XLM") to Self::codes_match_case_insensitive(&asset.code, &String::from_str(&env, "XLM")).
  • Test proving a lowercase/mixed-case "xlm" (with any issuer) is rejected the same way an uppercase duplicate would be, before the real native XLM is ever registered: Verified in test_case_variant_xlm_with_issuer_rejected_and_cannot_squat_native that "xlm" and "Xlm" with an issuer return Err(Ok(WalletError::InvalidAssetInfo)).
  • Test proving native XLM registration still succeeds normally when no case-variant squat exists (no regression to the happy path): Verified in test_case_variant_xlm_with_issuer_rejected_and_cannot_squat_native that subsequent registration of AssetInfo { code: "XLM", issuer: None } succeeds with Ok(Ok(())).
  • Decision on whether case-variant-of-XLM-with-issuer should be rejected outright, written out in the PR: Documented above in Root Cause and What Changed; rejected outright with InvalidAssetInfo.
  • cargo test --workspace output pasted: Pasted below under Evidence.

Evidence this actually runs

running 87 tests
test tests::test_accept_admin_emits_only_admin_transferred_not_recovery_completed ... ok
test tests::test_accept_by_wrong_address_fails ... ok
test tests::test_add_duplicate_guardian_fails ... ok
test tests::test_add_duplicate_asset_fails ... ok
test tests::test_add_asset_empty_code_fails ... ok
test tests::test_add_asset_overlong_code_fails ... ok
test tests::test_add_and_list_guardians ... ok
test tests::test_add_allowed_token_requires_admin ... ok
test tests::test_add_and_get_assets ... ok
test tests::test_add_asset_case_variant_duplicate_fails ... ok
test tests::test_daily_spent_survives_temporary_ttl_eviction ... ok
test tests::test_case_variant_xlm_with_issuer_rejected_and_cannot_squat_native ... ok
test tests::test_cancel_admin_transfer ... ok
test tests::test_admin_can_cancel_recovery_even_after_quorum ... ok
test tests::test_initialize ... ok
test tests::test_cannot_initiate_second_recovery_while_one_pending ... ok
test tests::test_execute_recovery_emits_both_admin_transferred_and_recovery_completed ... ok
test tests::test_execute_recovery_emits_recovery_completed_with_full_over_quorum_guardian_set ... ok
test tests::test_migrate_user_assets_trims_excess ... ok
test tests::test_migrate_user_assets_within_limit_does_nothing ... ok
test tests::test_native_code_with_issuer_is_contradictory_and_rejected ... ok
test tests::test_initialize_twice_fails ... ok
test tests::test_migrate_user_assets_requires_admin ... ok
test tests::test_no_limit_allows_any_spend ... ok
test tests::test_double_approval_rejected ... ok
test tests::test_execute_recovery_rejects_new_admin_same_as_current_admin ... ok
test tests::test_propose_upgrade_accepts_any_hash_without_validation ... ok
test tests::test_pending_admin_cleared_after_accept_admin ... ok
test tests::test_propose_upgrade_rejects_delay_below_minimum ... ok
test tests::test_non_native_code_without_issuer_is_underspecified_and_rejected ... ok
test tests::test_non_admin_cannot_add_guardian ... ok
test tests::test_propose_without_accept_keeps_admin_unchanged ... ok
test tests::test_execute_upgrade_with_never_uploaded_hash_traps - should panic ... ok
test tests::test_propose_upgrade_rejects_zero_delay ... ok
test tests::test_propose_upgrade_requires_admin ... ok
test tests::test_max_guardians_limit ... ok
test tests::test_propose_upgrade_accepts_delay_at_minimum - should panic ... ok
test tests::test_propose_and_execute_upgrade - should panic ... ok
test tests::test_record_spend_boundary_last_second_of_day_accumulates ... ok
test tests::test_record_spend_boundary_drift_awareness ... ok
test tests::test_record_spend_boundary_first_second_of_new_day_resets ... ok
test tests::test_raise_spend_then_lower_limit ... ok
test tests::test_record_spend_exact_day_boundary ... ok
test tests::test_record_spend_bucket_is_integer_division ... ok
test tests::test_record_spend_exceeds_limit_fails ... ok
test tests::test_record_spend_negative_amount_fails ... ok
test tests::test_record_spend_zero_amount_fails ... ok
test tests::test_record_spend_negative_amount_cannot_bypass_daily_limit ... ok
test tests::test_record_spend_within_limit ... ok
test tests::test_record_spend_overflow_does_not_poison_later_calls ... ok
test tests::test_record_spend_rejected_negative_amount_does_not_mutate_state ... ok
test tests::test_recovery_clears_any_in_flight_normal_admin_transfer ... ok
test tests::test_recovery_rejects_non_guardian ... ok
test tests::test_remove_asset ... ok
test tests::test_remove_guardian_below_threshold_fails ... ok
test tests::test_remove_nonexistent_asset_fails ... ok
test tests::test_recovery_happy_path_2_of_3 ... ok
test tests::test_remove_allowed_token ... ok
test tests::test_require_admin_not_initialized ... ok
test tests::test_revoke_recovery_approval_rejects_non_guardian ... ok
test tests::test_max_assets_limit ... ok
test tests::test_revoke_recovery_approval_rejects_removed_guardian ... ok
test tests::test_remove_guardian_who_never_approved_leaves_proposal_untouched ... ok
test tests::test_remove_guardian_dequorated_proposal_can_requorum_with_fresh_timelock ... ok
test tests::test_send_rejects_disallowed_token ... ok
test tests::test_removed_guardian_approval_no_longer_counts_toward_quorum ... ok
test tests::test_removed_guardian_cannot_initiate_recovery ... ok
test tests::test_send_over_daily_limit_fails_and_moves_no_tokens ... ok
test tests::test_revoking_approval_below_threshold_resets_timelock ... ok
test tests::test_send_happy_path_moves_tokens_and_records_spend ... ok
test tests::test_set_recovery_config_rejects_single_guardian_threshold ... ok
test tests::test_set_recovery_config_rejects_threshold_above_guardian_count ... ok
test tests::test_set_recovery_config_rejects_delay_below_minimum ... ok
test tests::test_set_recovery_config_rejects_zero_delay ... ok
test tests::test_set_recovery_config_accepts_delay_at_minimum_and_recovery_executes ... ok
test tests::test_spend_limit_ttl_extension_after_long_idle_period ... ok
test tests::test_spend_limit_set_and_get ... ok
test tests::test_send_rejects_when_token_wrapper_not_set ... ok
test tests::test_set_token_wrapper_requires_admin ... ok
test tests::test_set_recovery_config_rejected_while_recovery_pending ... ok
test tests::test_set_recovery_config_requires_min_guardians ... ok
test tests::test_transfer_admin ... ok
test tests::test_upgrade_rejects_hash_mismatch ... ok
test tests::test_upgrade_propose_double_fails ... ok
test tests::test_upgrade_requires_admin_and_ready_time ... ok
test tests::test_user_assets_ttl_extension_after_long_idle_period ... ok
test tests::test_send_rejects_reentrant_malicious_token ... ok

test result: ok. 87 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.70s

Tests

Added test_case_variant_xlm_with_issuer_rejected_and_cannot_squat_native asserting:

  1. add_asset with lowercase "xlm" and Some(fake_issuer) fails with WalletError::InvalidAssetInfo.
  2. add_asset with mixed-case "Xlm" and Some(fake_issuer) fails with WalletError::InvalidAssetInfo.
  3. Subsequent add_asset with native XLM (code: "XLM", issuer: None) succeeds with Ok(()).

Regression check

Existing tests verifying native and issued asset rules (test_native_code_with_issuer_is_contradictory_and_rejected, test_non_native_code_without_issuer_is_underspecified_and_rejected, test_add_and_get_assets, test_add_asset_case_variant_duplicate_fails) were run and all 87 tests passed without failure.

Checklist

  • Every Definition of done bullet above is checked and explained, not just checked
  • Evidence block above is filled in with real output, not omitted
  • New/updated tests are included and shown passing
  • No leftover console.log/TODO/debug code
  • Related/adjacent behavior re-verified, not assumed unaffected

closes Orbit-Wal#86)

- Align is_native check in add_asset with duplicate detection via codes_match_case_insensitive
- Reject any case-variant of XLM that specifies an issuer with InvalidAssetInfo
- Prevent malicious or accidental pre-squatting of case-variant 'xlm' from blocking native XLM registration
- Add test proving lowercase/mixed-case 'xlm' with issuer is rejected and real native XLM registration succeeds
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant