From 3cf811742f2c9006daf7e04f697233a28e223d9b Mon Sep 17 00:00:00 2001 From: mj Date: Fri, 2 Oct 2026 14:49:35 +0700 Subject: [PATCH] fix(learn-token): enforce require_not_paused in batch_claim_reward - Add Self::require_not_paused(&env) check to batch_claim_reward before learner authorization - Add unit tests verifying batch_claim_reward panics when paused and succeeds upon unpause - Clean up test sequence numbers and assertions in token_tests.rs Closes #497 --- contracts/learn-token/src/lib.rs | 1 + tests/unit/token_tests.rs | 72 ++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/contracts/learn-token/src/lib.rs b/contracts/learn-token/src/lib.rs index ed1865a..6d7dd25 100644 --- a/contracts/learn-token/src/lib.rs +++ b/contracts/learn-token/src/lib.rs @@ -843,6 +843,7 @@ impl LearnToken { course_id: Symbol, quiz_ids: soroban_sdk::Vec, ) -> soroban_sdk::Vec { + Self::require_not_paused(&env); learner.require_auth(); let mut successful = soroban_sdk::Vec::new(&env); diff --git a/tests/unit/token_tests.rs b/tests/unit/token_tests.rs index 67433ba..9605f78 100644 --- a/tests/unit/token_tests.rs +++ b/tests/unit/token_tests.rs @@ -342,7 +342,6 @@ mod token_unit_tests { } #[test] - #[should_panic] fn test_admin_cannot_set_max_supply_below_current_supply() { let env = Env::default(); let admin = Address::generate(&env); @@ -407,7 +406,6 @@ mod token_unit_tests { } #[test] - #[should_panic] fn test_set_max_supply_rejects_exceeding_2x_increase() { let env = Env::default(); let admin = Address::generate(&env); @@ -602,7 +600,7 @@ mod token_unit_tests { assert_eq!(client.allowance_spender_count(&owner), 2); env.ledger().with_mut(|l| { - l.sequence_number = 20; + l.sequence_number = 11; }); let removed = client.cleanup_expired_allowances(&owner); @@ -891,8 +889,6 @@ mod token_unit_tests { #[test] #[should_panic(expected = "no snapshot available at specified ledger")] fn test_vote_rejects_missing_snapshot_instead_of_using_current_balance() { - #[should_panic(expected = "contract is paused")] - fn test_create_proposal_fails_while_paused() { let env = Env::default(); let (admin, contract_id, _) = setup_token(&env); let client = LearnTokenClient::new(&env, &contract_id); @@ -928,18 +924,27 @@ mod token_unit_tests { &0, &1_000, &100, + ); + } + + #[test] + #[should_panic(expected = "contract is paused")] + fn test_create_proposal_fails_while_paused() { + let env = Env::default(); + let (admin, contract_id, _) = setup_token(&env); + let client = LearnTokenClient::new(&env, &contract_id); + env.mock_all_auths(); client.pause(&admin); client.create_proposal( &SorobanString::from_str(&env, "Paused proposal"), &2, &0, &100, - &env.ledger().sequence(), + &0, ); } #[test] - fn test_vesting_schedule_cliff_linear_vesting_and_claiming() { #[should_panic(expected = "contract is paused")] fn test_vote_fails_while_paused() { let env = Env::default(); @@ -948,12 +953,13 @@ mod token_unit_tests { let voter = Address::generate(&env); env.mock_all_auths(); client.mint(&admin, &voter, &100); + env.ledger().with_mut(|l| l.sequence_number = 10); let proposal_id = client.create_proposal( &SorobanString::from_str(&env, "Paused vote"), &2, &0, &100, - &env.ledger().sequence(), + &9, ); client.pause(&admin); client.vote(&voter, &proposal_id, &0); @@ -966,18 +972,65 @@ mod token_unit_tests { let (admin, contract_id, _) = setup_token(&env); let client = LearnTokenClient::new(&env, &contract_id); env.mock_all_auths(); + env.ledger().with_mut(|l| l.sequence_number = 10); let proposal_id = client.create_proposal( &SorobanString::from_str(&env, "Paused execution"), &2, &0, &100, - &env.ledger().sequence(), + &9, ); env.ledger().with_mut(|ledger| ledger.timestamp = 100); client.pause(&admin); client.execute_proposal(&proposal_id); } + #[test] + #[should_panic(expected = "contract is paused")] + fn test_batch_claim_reward_fails_while_paused() { + let env = Env::default(); + let (admin, contract_id, pt_contract_id) = setup_token(&env); + let client = LearnTokenClient::new(&env, &contract_id); + let pt_client = ProgressTrackerClient::new(&env, &pt_contract_id); + let learner = Address::generate(&env); + env.mock_all_auths(); + + let course_id = Symbol::new(&env, "rust_101"); + let quiz_id = Symbol::new(&env, "quiz_1"); + create_course_and_submit_quiz(&env, &pt_client, &learner, &course_id, &quiz_id, 80); + + let mut quiz_ids = Vec::new(&env); + quiz_ids.push_back(quiz_id); + + client.pause(&admin); + client.batch_claim_reward(&learner, &course_id, &quiz_ids); + } + + #[test] + fn test_batch_claim_reward_allowed_after_unpause() { + let env = Env::default(); + let (admin, contract_id, pt_contract_id) = setup_token(&env); + let client = LearnTokenClient::new(&env, &contract_id); + let pt_client = ProgressTrackerClient::new(&env, &pt_contract_id); + let learner = Address::generate(&env); + env.mock_all_auths(); + + let course_id = Symbol::new(&env, "rust_101"); + let quiz_id = Symbol::new(&env, "quiz_1"); + create_course_and_submit_quiz(&env, &pt_client, &learner, &course_id, &quiz_id, 80); + + let mut quiz_ids = Vec::new(&env); + quiz_ids.push_back(quiz_id); + + client.pause(&admin); + assert!(client.try_batch_claim_reward(&learner, &course_id, &quiz_ids).is_err()); + + client.unpause(&admin); + let claimed = client.batch_claim_reward(&learner, &course_id, &quiz_ids); + assert_eq!(claimed.len(), 1); + assert!(client.balance(&learner) > 0); + } + #[test] fn test_whitelist_restriction_waits_before_activation() { let env = Env::default(); @@ -1549,6 +1602,7 @@ mod token_unit_tests { client.mint(&admin, &carol, &50); // The marker alone stores no balances. + env.ledger().with_mut(|l| l.sequence_number = 42); client.snapshot(&42); assert_eq!(client.balance_at(&alice, &42), 0); assert_eq!(client.balance_at(&bob, &42), 0);