From b4265763444fdcbc7fef303d29be75dade916d26 Mon Sep 17 00:00:00 2001 From: Aman koli <2025.amana@isu.ac.in> Date: Sun, 31 May 2026 18:12:46 +0530 Subject: [PATCH 1/2] feat(badge-nft): add admin badge revocation for fraud prevention --- contracts/badge-nft/src/lib.rs | 68 ++++ contracts/badge-nft/src/test.rs | 54 ++++ .../test/test_get_badges_comprehensive.1.json | 296 ++++++++++++++++++ .../test/test_revoke_badge_emits_event.1.json | 278 ++++++++++++++++ .../test/test_revoke_badge_success.1.json | 253 +++++++++++++++ ...st_revoke_badge_unauthorized_caller.1.json | 215 +++++++++++++ 6 files changed, 1164 insertions(+) create mode 100644 contracts/badge-nft/test_snapshots/test/test_get_badges_comprehensive.1.json create mode 100644 contracts/badge-nft/test_snapshots/test/test_revoke_badge_emits_event.1.json create mode 100644 contracts/badge-nft/test_snapshots/test/test_revoke_badge_success.1.json create mode 100644 contracts/badge-nft/test_snapshots/test/test_revoke_badge_unauthorized_caller.1.json diff --git a/contracts/badge-nft/src/lib.rs b/contracts/badge-nft/src/lib.rs index 5666925..6a6a9e6 100644 --- a/contracts/badge-nft/src/lib.rs +++ b/contracts/badge-nft/src/lib.rs @@ -16,6 +16,14 @@ pub struct BadgeMinted { pub minted_at: u64, } +#[contractevent] +pub struct BadgeRevoked { + #[topic] + pub learner: Address, + #[topic] + pub course_id: u32, +} + #[contractimpl] impl BadgeNFT { /// Initializes the BadgeNFT contract with the authorized registry address. @@ -99,6 +107,66 @@ impl BadgeNFT { .publish(&env); } + /// Revokes a Soulbound Token (badge) from a learner's address. + /// Only the official protocol registry can trigger this for fraud prevention. + /// + /// # Arguments + /// * `admin` - The caller address (must be the authorized registry) + /// * `learner` - The learner address to revoke the badge from + /// * `course_id` - The course ID of the badge to revoke + /// + /// # Panics + /// * If caller authentication fails + /// * If caller is not the authorized registry + pub fn revoke_badge(env: Env, admin: Address, learner: Address, course_id: u32) { + // 1. admin.require_auth() + admin.require_auth(); + + // 2. Fetch 'Admin' (Registry) address from Instance storage. Assert caller == Admin. + let stored_admin: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .expect("Contract not initialized"); + assert!( + admin == stored_admin, + "Unauthorized: Caller is not the authorized registry" + ); + + // 3. Construct DataKey::UserBadges(learner). + let badges_key = DataKey::UserBadges(learner.clone()); + + // 4. Fetch existing Vec. + let mut badges: Vec = env + .storage() + .persistent() + .get(&badges_key) + .unwrap_or_else(|| Vec::new(&env)); + + // 5. Find the badge with course_id and remove it. + let mut found = false; + let mut index_to_remove = 0; + for (i, badge) in badges.iter().enumerate() { + if badge.course_id == course_id { + index_to_remove = i as u32; + found = true; + break; + } + } + + if found { + badges.remove(index_to_remove); + env.storage().persistent().set(&badges_key, &badges); + + // 6. Emit BadgeRevoked event. + BadgeRevoked { + learner, + course_id, + } + .publish(&env); + } + } + /// Returns all badges for a specific learner. /// /// # Arguments diff --git a/contracts/badge-nft/src/test.rs b/contracts/badge-nft/src/test.rs index 06bacf2..c6f72c9 100644 --- a/contracts/badge-nft/src/test.rs +++ b/contracts/badge-nft/src/test.rs @@ -239,6 +239,60 @@ fn test_mint_badge_timestamp_is_set() { assert_eq!(badge.minted_at, 0); } +// ── revoke_badge Tests ─────────────────────────────────────────────────────── + +#[test] +fn test_revoke_badge_success() { + let (env, client) = setup(); + let registry = Address::generate(&env); + let learner = Address::generate(&env); + + client.initialize(®istry); + client.mint_badge(®istry, &learner, &1); + + // Verify badge exists + assert_eq!(client.get_badge_count(&learner), 1); + + // Revoke badge + client.revoke_badge(®istry, &learner, &1); + + // Verify badge is removed + assert_eq!(client.get_badge_count(&learner), 0); +} + +#[test] +fn test_revoke_badge_emits_event() { + let (env, client) = setup(); + let registry = Address::generate(&env); + let learner = Address::generate(&env); + + client.initialize(®istry); + client.mint_badge(®istry, &learner, &1); + + client.revoke_badge(®istry, &learner, &1); + + let last_event = env.events().all().last().unwrap(); + let expected_topic: Vec = + (Symbol::new(&env, "badge_revoked"), &learner, 1u32).into_val(&env); + + assert_eq!(last_event.1, expected_topic); +} + +#[test] +#[should_panic(expected = "Unauthorized: Caller is not the authorized registry")] +fn test_revoke_badge_unauthorized_caller() { + let (env, client) = setup(); + let registry = Address::generate(&env); + let unauthorized_caller = Address::generate(&env); + let learner = Address::generate(&env); + + client.initialize(®istry); + client.mint_badge(®istry, &learner, &1); + + // Try to revoke with unauthorized caller - should panic + client.revoke_badge(&unauthorized_caller, &learner, &1); +} + // ── get_badges Tests ───────────────────────────────────────────────────────── #[test] diff --git a/contracts/badge-nft/test_snapshots/test/test_get_badges_comprehensive.1.json b/contracts/badge-nft/test_snapshots/test/test_get_badges_comprehensive.1.json new file mode 100644 index 0000000..a22de5c --- /dev/null +++ b/contracts/badge-nft/test_snapshots/test/test_get_badges_comprehensive.1.json @@ -0,0 +1,296 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "mint_badge", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 101 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "mint_badge", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 102 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "course_id" + }, + "val": { + "u32": 101 + } + }, + { + "key": { + "symbol": "minted_at" + }, + "val": { + "u64": "0" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "course_id" + }, + "val": { + "u32": 102 + } + }, + { + "key": { + "symbol": "minted_at" + }, + "val": { + "u64": "0" + } + } + ] + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/badge-nft/test_snapshots/test/test_revoke_badge_emits_event.1.json b/contracts/badge-nft/test_snapshots/test/test_revoke_badge_emits_event.1.json new file mode 100644 index 0000000..3cae657 --- /dev/null +++ b/contracts/badge-nft/test_snapshots/test/test_revoke_badge_emits_event.1.json @@ -0,0 +1,278 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "mint_badge", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "revoke_badge", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "badge_revoked" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ], + "data": { + "map": [] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/badge-nft/test_snapshots/test/test_revoke_badge_success.1.json b/contracts/badge-nft/test_snapshots/test/test_revoke_badge_success.1.json new file mode 100644 index 0000000..fe598c4 --- /dev/null +++ b/contracts/badge-nft/test_snapshots/test/test_revoke_badge_success.1.json @@ -0,0 +1,253 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "mint_badge", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "revoke_badge", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/badge-nft/test_snapshots/test/test_revoke_badge_unauthorized_caller.1.json b/contracts/badge-nft/test_snapshots/test/test_revoke_badge_unauthorized_caller.1.json new file mode 100644 index 0000000..fc16373 --- /dev/null +++ b/contracts/badge-nft/test_snapshots/test/test_revoke_badge_unauthorized_caller.1.json @@ -0,0 +1,215 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "mint_badge", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "UserBadges" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "course_id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "minted_at" + }, + "val": { + "u64": "0" + } + } + ] + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file From 00da42853e20967a08788a72242b2862077612c1 Mon Sep 17 00:00:00 2001 From: Aman koli <2025.amana@isu.ac.in> Date: Thu, 4 Jun 2026 10:37:37 +0530 Subject: [PATCH 2/2] chore: fix formatting for CI --- Cargo.lock | 1 + contracts/badge-nft/src/lib.rs | 8 ++---- contracts/stake-vault/src/lib.rs | 18 ++++++++++--- contracts/stake-vault/src/test.rs | 45 ++++++++++++++++++++++++------- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c20018f..38e3c65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" name = "course-registry" version = "0.1.0" dependencies = [ + "badge-nft", "soroban-sdk", ] diff --git a/contracts/badge-nft/src/lib.rs b/contracts/badge-nft/src/lib.rs index da67fd9..372db6f 100644 --- a/contracts/badge-nft/src/lib.rs +++ b/contracts/badge-nft/src/lib.rs @@ -168,13 +168,9 @@ mod contract_impl { if found { badges.remove(index_to_remove); env.storage().persistent().set(&badges_key, &badges); - + // 6. Emit BadgeRevoked event. - BadgeRevoked { - learner, - course_id, - } - .publish(&env); + BadgeRevoked { learner, course_id }.publish(&env); } } diff --git a/contracts/stake-vault/src/lib.rs b/contracts/stake-vault/src/lib.rs index d2c9e8b..352a03b 100644 --- a/contracts/stake-vault/src/lib.rs +++ b/contracts/stake-vault/src/lib.rs @@ -35,7 +35,14 @@ impl StakeVault { } pub fn get_multiplier(env: Env, user: Address) -> u32 { - let stake_info = env.storage().persistent().get(&DataKey::UserStake(user)).unwrap_or(StakeInfo { amount: 0, lock_timestamp: 0 }); + let stake_info = env + .storage() + .persistent() + .get(&DataKey::UserStake(user)) + .unwrap_or(StakeInfo { + amount: 0, + lock_timestamp: 0, + }); if stake_info.amount >= 500 { 200 } else if stake_info.amount >= 100 { @@ -56,9 +63,14 @@ impl StakeVault { .expect("Not initialized"); assert!(admin == stored_admin, "Unauthorized"); - env.deployer().update_current_contract_wasm(new_wasm_hash.clone()); + env.deployer() + .update_current_contract_wasm(new_wasm_hash.clone()); - ContractUpgraded { admin, new_wasm_hash }.publish(&env); + ContractUpgraded { + admin, + new_wasm_hash, + } + .publish(&env); } } diff --git a/contracts/stake-vault/src/test.rs b/contracts/stake-vault/src/test.rs index 78f89e0..5276923 100644 --- a/contracts/stake-vault/src/test.rs +++ b/contracts/stake-vault/src/test.rs @@ -14,37 +14,62 @@ fn test_get_multiplier() { assert_eq!(client.get_multiplier(&user), 100); // Set stake to 50 (< 100) - let stake_info = StakeInfo { amount: 50, lock_timestamp: 0 }; + let stake_info = StakeInfo { + amount: 50, + lock_timestamp: 0, + }; env.as_contract(&contract_id, || { - env.storage().persistent().set(&DataKey::UserStake(user.clone()), &stake_info); + env.storage() + .persistent() + .set(&DataKey::UserStake(user.clone()), &stake_info); }); assert_eq!(client.get_multiplier(&user), 100); // Set stake to 100 (100 <= x < 500) - let stake_info = StakeInfo { amount: 100, lock_timestamp: 0 }; + let stake_info = StakeInfo { + amount: 100, + lock_timestamp: 0, + }; env.as_contract(&contract_id, || { - env.storage().persistent().set(&DataKey::UserStake(user.clone()), &stake_info); + env.storage() + .persistent() + .set(&DataKey::UserStake(user.clone()), &stake_info); }); assert_eq!(client.get_multiplier(&user), 120); // Set stake to 499 (100 <= x < 500) - let stake_info = StakeInfo { amount: 499, lock_timestamp: 0 }; + let stake_info = StakeInfo { + amount: 499, + lock_timestamp: 0, + }; env.as_contract(&contract_id, || { - env.storage().persistent().set(&DataKey::UserStake(user.clone()), &stake_info); + env.storage() + .persistent() + .set(&DataKey::UserStake(user.clone()), &stake_info); }); assert_eq!(client.get_multiplier(&user), 120); // Set stake to 500 (x >= 500) - let stake_info = StakeInfo { amount: 500, lock_timestamp: 0 }; + let stake_info = StakeInfo { + amount: 500, + lock_timestamp: 0, + }; env.as_contract(&contract_id, || { - env.storage().persistent().set(&DataKey::UserStake(user.clone()), &stake_info); + env.storage() + .persistent() + .set(&DataKey::UserStake(user.clone()), &stake_info); }); assert_eq!(client.get_multiplier(&user), 200); // Set stake to 1000 (x >= 500) - let stake_info = StakeInfo { amount: 1000, lock_timestamp: 0 }; + let stake_info = StakeInfo { + amount: 1000, + lock_timestamp: 0, + }; env.as_contract(&contract_id, || { - env.storage().persistent().set(&DataKey::UserStake(user.clone()), &stake_info); + env.storage() + .persistent() + .set(&DataKey::UserStake(user.clone()), &stake_info); }); assert_eq!(client.get_multiplier(&user), 200); }