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
5 changes: 5 additions & 0 deletions contract/contract/src/base/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ pub fn contract_unpaused(env: &Env, admin: Address, timestamp: u64) {
env.events().publish(topics, timestamp);
}

pub fn admin_renounced(env: &Env, admin: Address) {
let topics = (Symbol::new(env, "admin_renounced"), admin);
env.events().publish(topics, ());
}

pub fn emergency_contact_updated(env: &Env, admin: Address, contact: Address) {
let topics = (Symbol::new(env, "emergency_contact_updated"), admin);
env.events().publish(topics, contact);
Expand Down
15 changes: 14 additions & 1 deletion contract/contract/src/crowdfunding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,19 @@ impl CrowdfundingTrait for CrowdfundingContract {
Ok(())
}

fn renounce_admin(env: Env) -> Result<(), CrowdfundingError> {
let admin: Address = env
.storage()
.instance()
.get(&StorageKey::Admin)
.ok_or(CrowdfundingError::NotInitialized)?;
admin.require_auth();

env.storage().instance().remove(&StorageKey::Admin);
events::admin_renounced(&env, admin);
Ok(())
}

fn is_paused(env: Env) -> bool {
env.storage()
.instance()
Expand Down Expand Up @@ -906,7 +919,7 @@ impl CrowdfundingTrait for CrowdfundingContract {
.storage()
.instance()
.get(&metrics_key)
.unwrap_or(PoolMetrics::new());
.unwrap_or_default();

metrics.total_raised -= contribution.amount;
// Note: We don't decrement contributor_count as the contributor may have other contributions
Expand Down
2 changes: 2 additions & 0 deletions contract/contract/src/interfaces/crowdfunding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ pub trait CrowdfundingTrait {

fn is_closed(env: Env, pool_id: u64) -> Result<bool, CrowdfundingError>;

fn renounce_admin(env: Env) -> Result<(), CrowdfundingError>;

fn get_active_campaign_count(env: Env) -> u32;
fn verify_cause(env: Env, cause: Address) -> Result<(), CrowdfundingError>;

Expand Down
1 change: 1 addition & 0 deletions contract/contract/test/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod close_pool_test;
mod create_pool;
mod crowdfunding_test;
mod renounce_admin_test;
mod verify_cause;
89 changes: 89 additions & 0 deletions contract/contract/test/renounce_admin_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#![cfg(test)]

use soroban_sdk::{
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, Env, IntoVal,
};

use crate::{
base::errors::CrowdfundingError,
crowdfunding::{CrowdfundingContract, CrowdfundingContractClient},
};

fn setup_test(env: &Env) -> (CrowdfundingContractClient<'_>, Address, Address) {
env.mock_all_auths();
let contract_id = env.register(CrowdfundingContract, ());
let client = CrowdfundingContractClient::new(env, &contract_id);

let admin = Address::generate(env);
let token_admin = Address::generate(env);
let token_contract = env.register_stellar_asset_contract_v2(token_admin.clone());
let token_address = token_contract.address();

client.initialize(&admin, &token_address, &0);

(client, admin, token_address)
}

#[test]
fn test_renounce_admin_success() {
let env = Env::default();
let (client, _, _) = setup_test(&env);

// Initial state: admin exists and can perform admin actions
assert!(!client.is_paused());
client.pause();
assert!(client.is_paused());
client.unpause();
assert!(!client.is_paused());

// Renounce admin
client.renounce_admin();

// Verify admin is removed by trying an admin action
let result = client.try_pause();
assert_eq!(result, Err(Ok(CrowdfundingError::NotInitialized)));
}

#[test]
fn test_renounce_admin_unauthorized() {
let env = Env::default();
let (client, _, _) = setup_test(&env);
let _non_admin = Address::generate(&env);

// unauthorized call is covered by require_auth in renounce_admin
// which is tested below via mock_auths
}

#[test]
#[should_panic]
fn test_renounce_admin_requires_admin_auth() {
let env = Env::default();
let (client, _, _) = setup_test(&env);
let non_admin = Address::generate(&env);

// Use specific mock_auths to ensure non_admin is the one calling
client
.mock_auths(&[MockAuth {
address: &non_admin,
invoke: &MockAuthInvoke {
contract: &client.address,
fn_name: "renounce_admin",
args: ().into_val(&env),
sub_invokes: &[],
},
}])
.renounce_admin();
}

#[test]
fn test_renounce_admin_already_renounced() {
let env = Env::default();
let (client, _, _) = setup_test(&env);

client.renounce_admin();

// Try to renounce again
let result = client.try_renounce_admin();
assert_eq!(result, Err(Ok(CrowdfundingError::NotInitialized)));
}
Loading