Skip to content

Commit f485005

Browse files
fix: contract: Social Payment: Integrate username registry lookup directly into pay function logic (#1083)
* fix: resolve issue #965 * fix: repair CI for issue #965 * fix: repair CI for issue #965 --------- Co-authored-by: issue-solver-bot <issue-solver-bot@users.noreply.github.com>
1 parent 8172cb9 commit f485005

2 files changed

Lines changed: 86 additions & 47 deletions

File tree

‎backend/Cargo.lock‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎contracts/contracts/social_payment/src/lib.rs‎

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -410,25 +410,13 @@ impl SocialPaymentContract {
410410

411411
// ── Payment methods ───────────────────────────────────────────────────────
412412

413-
/// SC-005: Execute a P2P social payment using the configured Naira token.
414-
/// For Public payments a 0.1% platform fee is routed to the treasury.
415-
/// For Friends/Private payments the full amount goes to the receiver.
413+
/// SC-005 / Issue #965: Execute a P2P social payment using the configured
414+
/// Naira token. The recipient is identified by their registered username,
415+
/// which is resolved to an on-chain address via the configured UserRegistry
416+
/// contract before routing the payment. For Public payments a 0.1% platform
417+
/// fee is routed to the treasury. For Friends/Private payments the full
418+
/// amount goes to the receiver.
416419
pub fn pay(
417-
env: Env,
418-
sender: Address,
419-
receiver: Address,
420-
token: Address,
421-
amount: i128,
422-
memo: String,
423-
visibility: Visibility,
424-
) {
425-
sender.require_auth();
426-
execute_payment(env, sender, receiver, token, amount, memo, visibility);
427-
}
428-
429-
/// Issue #519: Accept a recipient username, resolve the on-chain address via the
430-
/// configured UserRegistry contract, then execute the standard payment flow.
431-
pub fn pay_by_username(
432420
env: Env,
433421
sender: Address,
434422
recipient_username: String,
@@ -446,11 +434,35 @@ impl SocialPaymentContract {
446434
.expect("user registry not configured");
447435

448436
let registry = UserRegistryClient::new(&env, &registry_id);
437+
// Username resolution — panics with "username not found" if unregistered.
449438
let receiver = registry.get_address(&recipient_username);
450439

451440
execute_payment(env, sender, receiver, token, amount, memo, visibility);
452441
}
453442

443+
/// Issue #519: Backward-compatible alias for `pay` that accepts a recipient
444+
/// username, resolves the on-chain address via the configured UserRegistry
445+
/// contract, then executes the standard payment flow.
446+
pub fn pay_by_username(
447+
env: Env,
448+
sender: Address,
449+
recipient_username: String,
450+
token: Address,
451+
amount: i128,
452+
memo: String,
453+
visibility: Visibility,
454+
) {
455+
Self::pay(
456+
env,
457+
sender,
458+
recipient_username,
459+
token,
460+
amount,
461+
memo,
462+
visibility,
463+
);
464+
}
465+
454466
/// SC-037: Execute multiple payments in a single contract call.
455467
pub fn batch_pay(env: Env, sender: Address, payments: Vec<Payment>) {
456468
sender.require_auth();
@@ -852,14 +864,17 @@ mod tests {
852864
// ── Public payment: fee deducted, event emitted ──────────────────────────
853865
#[test]
854866
fn test_social_payment_public_visibility_deducts_fee() {
855-
let (env, client, admin, treasury, sender, receiver) = setup();
867+
let (env, client, admin, treasury, sender, receiver, registry_id) = setup_with_registry();
856868
let token = mint_token(&env, &admin, &sender, 10_000);
857869
client.set_naira_token(&token);
858870
let token_client = soroban_sdk::token::Client::new(&env, &token);
859871

872+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
873+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
874+
860875
client.pay(
861876
&sender,
862-
&receiver,
877+
&String::from_str(&env, "bob"),
863878
&token,
864879
&1000,
865880
&String::from_str(&env, "Public payment"),
@@ -899,14 +914,17 @@ mod tests {
899914
#[test]
900915
fn test_public_payment_fee_rounding_preserves_funds_at_boundaries() {
901916
for amount in [1i128, 1_000, i128::MAX] {
902-
let (env, client, admin, treasury, sender, receiver) = setup();
917+
let (env, client, admin, treasury, sender, receiver, registry_id) = setup_with_registry();
903918
let token = mint_token(&env, &admin, &sender, amount);
904919
client.set_naira_token(&token);
905920
let token_client = soroban_sdk::token::Client::new(&env, &token);
906921

922+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
923+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
924+
907925
client.pay(
908926
&sender,
909-
&receiver,
927+
&String::from_str(&env, "bob"),
910928
&token,
911929
&amount,
912930
&String::from_str(&env, "Rounding boundary"),
@@ -923,14 +941,17 @@ mod tests {
923941
// ── Private payment: no fee, full amount ─────────────────────────────────
924942
#[test]
925943
fn test_social_payment_private_visibility_no_fee() {
926-
let (env, client, admin, _treasury, sender, receiver) = setup();
944+
let (env, client, admin, _treasury, sender, receiver, registry_id) = setup_with_registry();
927945
let token = mint_token(&env, &admin, &sender, 10_000);
928946
client.set_naira_token(&token);
929947
let token_client = soroban_sdk::token::Client::new(&env, &token);
930948

949+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
950+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
951+
931952
client.pay(
932953
&sender,
933-
&receiver,
954+
&String::from_str(&env, "bob"),
934955
&token,
935956
&1000,
936957
&String::from_str(&env, "Private"),
@@ -944,14 +965,17 @@ mod tests {
944965
// ── Friends-only payment: no fee ─────────────────────────────────────────
945966
#[test]
946967
fn test_social_payment_friends_visibility_no_fee() {
947-
let (env, client, admin, treasury, sender, receiver) = setup();
968+
let (env, client, admin, treasury, sender, receiver, registry_id) = setup_with_registry();
948969
let token = mint_token(&env, &admin, &sender, 5_000);
949970
client.set_naira_token(&token);
950971
let token_client = soroban_sdk::token::Client::new(&env, &token);
951972

973+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
974+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
975+
952976
client.pay(
953977
&sender,
954-
&receiver,
978+
&String::from_str(&env, "bob"),
955979
&token,
956980
&500,
957981
&String::from_str(&env, "Friends"),
@@ -967,12 +991,14 @@ mod tests {
967991
#[test]
968992
#[ignore]
969993
fn test_pay_rejects_zero_amount() {
970-
let (env, client, admin, _treasury, sender, receiver) = setup();
994+
let (env, client, admin, _treasury, sender, receiver, registry_id) = setup_with_registry();
971995
let token = mint_token(&env, &admin, &sender, 1_000);
972996
client.set_naira_token(&token);
997+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
998+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
973999
let res = client.try_pay(
9741000
&sender,
975-
&receiver,
1001+
&String::from_str(&env, "bob"),
9761002
&token,
9771003
&0,
9781004
&String::from_str(&env, "bad"),
@@ -1184,15 +1210,18 @@ mod tests {
11841210
#[test]
11851211
#[ignore] // pre-existing: assert!(..) in Soroban v20 causes non-unwinding panic
11861212
fn test_public_payment_rejects_non_naira_token_for_fee() {
1187-
let (env, client, admin, treasury, sender, receiver) = setup();
1213+
let (env, client, admin, treasury, sender, receiver, registry_id) = setup_with_registry();
11881214
let naira_token = mint_token(&env, &admin, &sender, 10_000);
11891215
let junk_token = mint_token(&env, &admin, &sender, 10_000);
11901216
let junk_client = soroban_sdk::token::Client::new(&env, &junk_token);
11911217
client.set_naira_token(&naira_token);
11921218

1219+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
1220+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
1221+
11931222
let res = client.try_pay(
11941223
&sender,
1195-
&receiver,
1224+
&String::from_str(&env, "bob"),
11961225
&junk_token,
11971226
&1000,
11981227
&String::from_str(&env, "Junk public payment"),
@@ -1208,12 +1237,15 @@ mod tests {
12081237
#[test]
12091238
#[ignore] // pre-existing: .expect() in Soroban v20 causes non-unwinding panic
12101239
fn test_pay_rejects_when_naira_token_not_configured() {
1211-
let (env, client, admin, _treasury, sender, receiver) = setup();
1240+
let (env, client, admin, _treasury, sender, receiver, registry_id) = setup_with_registry();
12121241
let token = mint_token(&env, &admin, &sender, 1_000);
12131242

1243+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
1244+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
1245+
12141246
let res = client.try_pay(
12151247
&sender,
1216-
&receiver,
1248+
&String::from_str(&env, "bob"),
12171249
&token,
12181250
&100,
12191251
&String::from_str(&env, "Missing config"),
@@ -1226,11 +1258,14 @@ mod tests {
12261258
// ── Adjust fee coefficient: updates value, affects payout calculation ─────
12271259
#[test]
12281260
fn test_adjust_fee_coefficient() {
1229-
let (env, client, admin, treasury, sender, receiver) = setup();
1261+
let (env, client, admin, treasury, sender, receiver, registry_id) = setup_with_registry();
12301262
let token = mint_token(&env, &admin, &sender, 10_000);
12311263
client.set_naira_token(&token);
12321264
let token_client = soroban_sdk::token::Client::new(&env, &token);
12331265

1266+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
1267+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
1268+
12341269
// Verify default is 10 (0.1%)
12351270
assert_eq!(client.fee_coefficient(), 10);
12361271

@@ -1240,7 +1275,7 @@ mod tests {
12401275

12411276
client.pay(
12421277
&sender,
1243-
&receiver,
1278+
&String::from_str(&env, "bob"),
12441279
&token,
12451280
&1000,
12461281
&String::from_str(&env, "Public payment"),
@@ -1627,7 +1662,7 @@ mod tests {
16271662

16281663
client.pay(
16291664
&sender,
1630-
&receiver,
1665+
&String::from_str(&env, "bob"),
16311666
&token,
16321667
&1000,
16331668
&String::from_str(&env, "hi"),
@@ -1651,14 +1686,18 @@ mod tests {
16511686
// ── SC-042: no registry / unregistered participants never break a payment ─
16521687
#[test]
16531688
fn test_pay_event_username_defaults_to_empty_when_unregistered() {
1654-
// No registry configured at all — resolve_username must short-circuit.
1655-
let (env, client, admin, _treasury, sender, receiver) = setup();
1689+
// Registry configured but neither participant registered a username —
1690+
// resolve_username must fall back to empty strings.
1691+
let (env, client, admin, _treasury, sender, receiver, registry_id) = setup_with_registry();
16561692
let token = mint_token(&env, &admin, &sender, 10_000);
16571693
client.set_naira_token(&token);
16581694

1695+
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
1696+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
1697+
16591698
client.pay(
16601699
&sender,
1661-
&receiver,
1700+
&String::from_str(&env, "bob"),
16621701
&token,
16631702
&500,
16641703
&String::from_str(&env, "hi"),
@@ -1679,20 +1718,20 @@ mod tests {
16791718
assert!(found, "SocialPaymentEvent not emitted");
16801719
}
16811720

1682-
// ── SC-042: registry configured but receiver never registered a username ──
1721+
// ── SC-042: registry configured but sender never registered a username ────
16831722
#[test]
16841723
fn test_pay_event_username_defaults_to_empty_for_unregistered_participant() {
16851724
let (env, client, admin, _treasury, sender, receiver, registry_id) = setup_with_registry();
16861725
let token = mint_token(&env, &admin, &sender, 10_000);
16871726
client.set_naira_token(&token);
16881727

16891728
let registry_client = MockUserRegistryClient::new(&env, &registry_id);
1690-
registry_client.register(&sender, &String::from_str(&env, "alice"));
1691-
// receiver intentionally left unregistered.
1729+
// sender intentionally left unregistered.
1730+
registry_client.register(&receiver, &String::from_str(&env, "bob"));
16921731

16931732
client.pay(
16941733
&sender,
1695-
&receiver,
1734+
&String::from_str(&env, "bob"),
16961735
&token,
16971736
&500,
16981737
&String::from_str(&env, "hi"),
@@ -1705,8 +1744,8 @@ mod tests {
17051744
for item in events.iter() {
17061745
if item.1.contains(topic) {
17071746
let ev: SocialPaymentEvent = item.2.try_into_val(&env).unwrap();
1708-
assert_eq!(ev.sender_username, String::from_str(&env, "alice"));
1709-
assert_eq!(ev.receiver_username, String::from_str(&env, ""));
1747+
assert_eq!(ev.sender_username, String::from_str(&env, ""));
1748+
assert_eq!(ev.receiver_username, String::from_str(&env, "bob"));
17101749
found = true;
17111750
}
17121751
}

0 commit comments

Comments
 (0)