Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion contracts/reward-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod types;
pub trait RewardPoolInterface {
fn initialize(env: Env, admin: Address, token: Address);
fn add_approved_spender(env: Env, admin: Address, spender: Address);
fn remove_approved_spender(env: Env, admin: Address, spender: Address);
fn set_pause(env: Env, admin: Address, status: bool);
fn distribute_reward(env: Env, caller: Address, learner: Address, amount: i128);
fn fund_pool(env: Env, donor: Address, amount: i128);
Expand All @@ -28,6 +29,12 @@ pub struct SpenderAdded {
pub spender: Address,
}

#[contractevent]
pub struct SpenderRemoved {
#[topic]
pub spender: Address,
}

#[contractevent]
pub struct RewardDistributed {
#[topic]
Expand Down Expand Up @@ -67,7 +74,7 @@ mod contract_impl {
use crate::types::DataKey;
use crate::{
ContractUpgraded, EmergencySweep, PoolFunded, PoolInitialized, RewardDistributed,
SpenderAdded,
SpenderAdded, SpenderRemoved,
};

#[contract]
Expand Down Expand Up @@ -138,6 +145,36 @@ mod contract_impl {
SpenderAdded { spender }.publish(&env);
}

/// Removes a contract address from the approved spender whitelist.
///
/// # Arguments
/// * `admin` - The admin address (must match stored admin)
/// * `spender` - The contract address to remove from the whitelist
///
/// # Panics
/// * If contract is not initialized
/// * If admin does not match stored admin
/// * If admin authentication fails
pub fn remove_approved_spender(env: Env, admin: Address, spender: Address) {
let stored_admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.expect("Not initialized");

if admin != stored_admin {
panic!("Unauthorized");
}

admin.require_auth();

env.storage()
.persistent()
.set(&DataKey::Spender(spender.clone()), &false);

SpenderRemoved { spender }.publish(&env);
}

/// Toggles the pause state of the contract (emergency circuit breaker).
///
/// # Arguments
Expand Down
79 changes: 79 additions & 0 deletions contracts/reward-pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,85 @@ fn test_distribute_reward_unauthorized_spender() {
client.distribute_reward(&unauthorized_spender, &learner, &100);
}

#[test]
#[should_panic(expected = "Caller is not an authorized spender")]
fn test_distribute_reward_revoked_spender() {
let (env, client) = setup();
let admin = Address::generate(&env);
let spender = Address::generate(&env);
let learner = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

client.initialize(&admin, &token_id.address());
client.add_approved_spender(&admin, &spender);
client.remove_approved_spender(&admin, &spender);

client.distribute_reward(&spender, &learner, &100);
}

#[test]
fn test_revoking_spender_does_not_affect_other_approved_spender() {
let (env, client) = setup();
let admin = Address::generate(&env);
let spender_a = Address::generate(&env);
let spender_b = Address::generate(&env);
let learner = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

client.initialize(&admin, &token_id.address());
client.add_approved_spender(&admin, &spender_a);
client.add_approved_spender(&admin, &spender_b);
client.remove_approved_spender(&admin, &spender_a);

let token_client = token::StellarAssetClient::new(&env, &token_id.address());
token_client.mint(&client.address, &100);

client.distribute_reward(&spender_b, &learner, &100);

assert_eq!(token_client.balance(&learner), 100);
}

#[test]
fn test_remove_approved_spender_wrong_admin_preserves_approval() {
let (env, client) = setup();
let admin = Address::generate(&env);
let wrong_admin = Address::generate(&env);
let spender = Address::generate(&env);
let learner = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

client.initialize(&admin, &token_id.address());
client.add_approved_spender(&admin, &spender);

let result = client.try_remove_approved_spender(&wrong_admin, &spender);
assert!(result.is_err());

let token_client = token::StellarAssetClient::new(&env, &token_id.address());
token_client.mint(&client.address, &100);

client.distribute_reward(&spender, &learner, &100);

assert_eq!(token_client.balance(&learner), 100);
}

#[test]
#[should_panic(expected = "Caller is not an authorized spender")]
fn test_remove_approved_spender_repeated_revocation() {
let (env, client) = setup();
let admin = Address::generate(&env);
let spender = Address::generate(&env);
let learner = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

client.initialize(&admin, &token_id.address());
client.add_approved_spender(&admin, &spender);

client.remove_approved_spender(&admin, &spender);
client.remove_approved_spender(&admin, &spender);

client.distribute_reward(&spender, &learner, &100);
}

#[test]
#[should_panic(expected = "Not initialized")]
fn test_distribute_reward_not_initialized() {
Expand Down
Loading
Loading