From 020a292c36514d81960bf522207a06d7a2425774 Mon Sep 17 00:00:00 2001 From: Anichris winner Date: Tue, 18 Aug 2026 15:46:47 +0000 Subject: [PATCH] fix(referral_registry): reject unregistered referrers in register_referral Closes #71 - Add UnregisteredReferrer = 7 to ReferralError enum - Guard in register_referral validates the referrer is already a registered user before accepting the relationship; returns Err(ReferralError::UnregisteredReferrer) otherwise - Prevents fee routing to arbitrary/attacker-controlled addresses and stops inflation of ReferralCount for non-participants - Add test_reject_unregistered_referrer (expects Error #7) - Update test_register_with_referrer, test_credit_with_referrer, test_earnings_accumulation, test_referrer_bonus_points_accumulate, and test_referral_count_tracking to pre-register the referrer; fix points assertions that now include the referrer's welcome bonus --- referral_registry/src/lib.rs | 7 +++++ referral_registry/src/tests.rs | 52 +++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/referral_registry/src/lib.rs b/referral_registry/src/lib.rs index 38ac662..b43e3c7 100644 --- a/referral_registry/src/lib.rs +++ b/referral_registry/src/lib.rs @@ -19,6 +19,7 @@ pub enum ReferralError { AlreadyRegistered = 4, SelfReferral = 5, NotAdmin = 6, + UnregisteredReferrer = 7, } #[contracttype] @@ -123,6 +124,12 @@ impl ReferralRegistryContract { if *ref_addr == user { return Err(ReferralError::SelfReferral); } + // Referrer must already be a registered user; reject unregistered + // addresses to prevent fee routing to arbitrary/attacker-controlled + // addresses and to keep the referral graph within real participants. + if !Self::is_registered(env.clone(), ref_addr.clone()) { + return Err(ReferralError::UnregisteredReferrer); + } } // Lever A: write ONE packed Profile entry (display_name + referrer) // instead of the three legacy keys (Registered + DisplayName + Referrer). diff --git a/referral_registry/src/tests.rs b/referral_registry/src/tests.rs index c683cae..4840da9 100644 --- a/referral_registry/src/tests.rs +++ b/referral_registry/src/tests.rs @@ -86,6 +86,11 @@ fn test_register_with_referrer() { let user = Address::generate(&t.env); let referrer = Address::generate(&t.env); + // Referrer must be a registered user before they can refer others. + let no_ref: Option
= None; + t.client + .register_referral(&referrer, &String::from_str(&t.env, "Referrer"), &no_ref); + t.client.register_referral( &user, &String::from_str(&t.env, "CryptoKing"), @@ -152,6 +157,23 @@ fn test_reject_self_referral() { ); } +// ── 4b. Reject registration when the supplied referrer is not registered ────── + +#[test] +#[should_panic(expected = "Error(Contract, #7)")] +fn test_reject_unregistered_referrer() { + let t = setup(); + let user = Address::generate(&t.env); + // This address was never registered — must be rejected. + let unregistered_referrer = Address::generate(&t.env); + + t.client.register_referral( + &user, + &String::from_str(&t.env, "NewUser"), + &Some(unregistered_referrer), + ); +} + // ── 5. Reject double registration ──────────────────────────────────────────── #[test] @@ -201,6 +223,11 @@ fn test_credit_with_referrer() { let user = Address::generate(&t.env); let referrer = Address::generate(&t.env); + // Referrer must be registered before they can be used as a referrer. + let no_ref: Option
= None; + t.client + .register_referral(&referrer, &String::from_str(&t.env, "Referrer"), &no_ref); + // Register user with referrer t.client.register_referral( &user, @@ -222,9 +249,10 @@ fn test_credit_with_referrer() { let xlm_client = TokenClient::new(&t.env, &t.xlm_sac_id); assert_eq!(xlm_client.balance(&referrer), referral_fee); - // Referrer got 3 leaderboard bonus points + // Referrer got 3 leaderboard bonus points (plus 5 welcome pts from + // their own registration = 8 total). let lb_client = leaderboard::LeaderboardContractClient::new(&t.env, &t.leaderboard_id); - assert_eq!(lb_client.get_points(&referrer), 3); + assert_eq!(lb_client.get_points(&referrer), 8); // Earnings tracked assert_eq!(t.client.get_earnings(&referrer), referral_fee); @@ -286,6 +314,11 @@ fn test_earnings_accumulation() { let user = Address::generate(&t.env); let referrer = Address::generate(&t.env); + // Referrer must be registered first. + let no_ref: Option
= None; + t.client + .register_referral(&referrer, &String::from_str(&t.env, "Referrer"), &no_ref); + t.client.register_referral( &user, &String::from_str(&t.env, "Bettor"), @@ -312,6 +345,11 @@ fn test_referrer_bonus_points_accumulate() { let user = Address::generate(&t.env); let referrer = Address::generate(&t.env); + // Referrer must be registered first. + let no_ref: Option
= None; + t.client + .register_referral(&referrer, &String::from_str(&t.env, "Referrer"), &no_ref); + t.client.register_referral( &user, &String::from_str(&t.env, "Bettor"), @@ -322,13 +360,14 @@ fn test_referrer_bonus_points_accumulate() { let sac_admin = StellarAssetClient::new(&t.env, &t.xlm_sac_id); sac_admin.mint(&t.referral_id, &1000_0000000_i128); - // 3 credits → 3 × 3 = 9 bonus pts for referrer + // 3 credits → 3 × 3 = 9 bonus pts for referrer; plus 5 welcome pts + // from their own registration → 14 total. t.client.credit(&t.market, &user, &5_000_000_i128); t.client.credit(&t.market, &user, &5_000_000_i128); t.client.credit(&t.market, &user, &5_000_000_i128); let lb_client = leaderboard::LeaderboardContractClient::new(&t.env, &t.leaderboard_id); - assert_eq!(lb_client.get_points(&referrer), 9); // 3 × 3 pts + assert_eq!(lb_client.get_points(&referrer), 14); // 5 welcome + 3 × 3 bonus pts } // ── 11. Referral count tracking ────────────────────────────────────────────── @@ -338,6 +377,11 @@ fn test_referral_count_tracking() { let t = setup(); let referrer = Address::generate(&t.env); + // Referrer must be registered before they can refer others. + let no_ref: Option
= None; + t.client + .register_referral(&referrer, &String::from_str(&t.env, "Referrer"), &no_ref); + // 3 users register with the same referrer for _ in 0..3 { let user = Address::generate(&t.env);