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/Cargo.toml b/Cargo.toml index aec6ce3..e144bd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,8 @@ members = [ "contracts/reward-pool", "contracts/quest-engine", "contracts/governance", - "contracts/stake-vault" - "contracts/badge-nft", - "contracts/governance" + "contracts/stake-vault", + "contracts/badge-nft" ] [workspace.dependencies] diff --git a/contracts/badge-nft/src/lib.rs b/contracts/badge-nft/src/lib.rs index 8200bc6..372db6f 100644 --- a/contracts/badge-nft/src/lib.rs +++ b/contracts/badge-nft/src/lib.rs @@ -12,6 +12,7 @@ use types::Badge; pub trait BadgeNFTInterface { fn initialize(env: Env, admin: Address); fn mint_badge(env: Env, caller: Address, learner: Address, course_id: u32); + fn revoke_badge(env: Env, admin: Address, learner: Address, course_id: u32); fn get_badges(env: Env, learner: Address) -> Vec; fn get_badge_count(env: Env, learner: Address) -> u32; fn has_badge(env: Env, learner: Address, course_id: u32) -> bool; @@ -26,6 +27,14 @@ pub struct BadgeMinted { pub minted_at: u64, } +#[contractevent] +pub struct BadgeRevoked { + #[topic] + pub learner: Address, + #[topic] + pub course_id: u32, +} + #[contractevent] pub struct ContractUpgraded { #[topic] @@ -41,7 +50,7 @@ mod contract_impl { use soroban_sdk::{contract, contractimpl, Address, BytesN, Env, Vec}; use crate::types::{Badge, DataKey}; - use crate::{BadgeMinted, ContractUpgraded}; + use crate::{BadgeMinted, BadgeRevoked, ContractUpgraded}; #[contract] pub struct BadgeNFT; @@ -109,7 +118,69 @@ mod contract_impl { .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 + /// * `learner` - The learner address + /// + /// # Returns + /// Vector of Badge structs. Returns empty vector if learner has no badges. pub fn get_badges(env: Env, learner: Address) -> Vec { let badges_key = DataKey::UserBadges(learner); env.storage() @@ -119,12 +190,25 @@ mod contract_impl { } /// Returns the count of badges for a specific learner. + /// + /// # Arguments + /// * `learner` - The learner address + /// + /// # Returns + /// Number of badges the learner owns. pub fn get_badge_count(env: Env, learner: Address) -> u32 { let badges = Self::get_badges(env, learner); badges.len() } /// Checks if a learner has a specific badge. + /// + /// # Arguments + /// * `learner` - The learner address + /// * `course_id` - The course ID to check + /// + /// # Returns + /// true if the learner has the badge, false otherwise. pub fn has_badge(env: Env, learner: Address, course_id: u32) -> bool { let badges = Self::get_badges(env, learner); for badge in badges.iter() { 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_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 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); }