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
15 changes: 15 additions & 0 deletions src/budgetchain/Budget.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub mod Budget {
#[flat]
SRC5Event: SRC5Component::Event,
FundsRequested: FundsRequested,
OrganizationRemoved: OrganizationRemoved,
FundsReturned: FundsReturned,
}

Expand Down Expand Up @@ -156,6 +157,11 @@ pub mod Budget {
pub milestone_id: u64,
}

#[derive(Drop, starknet::Event)]
pub struct OrganizationRemoved {
pub org_id: u256,
}

#[constructor]
fn constructor(ref self: ContractState, default_admin: ContractAddress) {
assert(default_admin != contract_address_const::<0>(), ERROR_ZERO_ADDRESS);
Expand Down Expand Up @@ -735,7 +741,16 @@ pub mod Budget {
fn is_paused(self: @ContractState) -> bool {
self.is_paused.read()
}
fn remove_organization(ref self: ContractState, org_id: u256) {
let caller = get_caller_address();
assert(caller == self.admin.read(), ERROR_ONLY_ADMIN);

let mut org = self.organizations.read(org_id);
org.is_active = false;
self.organizations.write(org_id, org);

self.emit(OrganizationRemoved { org_id: org_id });
}
fn request_funds(
ref self: ContractState,
requester: ContractAddress,
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/IBudget.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub trait IBudget<TContractState> {
) -> u256;
fn get_organization(self: @TContractState, org_id: u256) -> Organization;
fn is_authorized_organization(self: @TContractState, org: ContractAddress) -> bool;
fn remove_organization(ref self: TContractState, org_id: u256);

// Fund Request Management
fn get_fund_request(self: @TContractState, project_id: u64, request_id: u64) -> FundRequest;
Expand Down
87 changes: 87 additions & 0 deletions tests/test_budgetchain.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -1440,3 +1440,90 @@ fn test_project_transaction_count_and_storage() {
assert(txs.get(1).unwrap().id == 1_u64, 'Second tx id should be 1');
assert(txs.get(2).unwrap().id == 2_u64, 'Third tx id should be 2');
}

#[test]
fn test_remove_organization_success() {
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

// Create an organization first
let org_name = 'Test Org';
let org_address = contract_address_const::<'Organization'>();
let org_mission = 'Testing Budget Chain';

// Set admin as caller to create organization
cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite);
let org_id = dispatcher.create_organization(org_name, org_address, org_mission);
stop_cheat_caller_address(admin_address);

// Verify organization is active before removal
let org_before = dispatcher.get_organization(org_id);
assert(org_before.is_active == true, 'be active before removal');

// Remove organization as admin
cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite);
dispatcher.remove_organization(org_id);
stop_cheat_caller_address(admin_address);

// Verify organization is inactive after removal
let org_after = dispatcher.get_organization(org_id);
assert(org_after.is_active == false, 'be inactive after removal');
}

#[test]
#[should_panic(expected: 'ONLY ADMIN')]
fn test_remove_organization_not_admin() {
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

// Create an organization first
let org_name = 'Test Org';
let org_address = contract_address_const::<'Organization'>();
let org_mission = 'Testing Budget Chain';

// Set admin as caller to create organization
cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite);
let org_id = dispatcher.create_organization(org_name, org_address, org_mission);
stop_cheat_caller_address(admin_address);

// Try to remove organization as non-admin
let non_admin = contract_address_const::<'non_admin'>();
cheat_caller_address(contract_address, non_admin, CheatSpan::Indefinite);
dispatcher.remove_organization(org_id);
stop_cheat_caller_address(non_admin);
}

#[test]
fn test_remove_organization_event_emission() {
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };
let mut spy = spy_events();

// Create an organization first
let org_name = 'Test Org';
let org_address = contract_address_const::<'Organization'>();
let org_mission = 'Testing Budget Chain';

// Set admin as caller to create organization
cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite);
let org_id = dispatcher.create_organization(org_name, org_address, org_mission);
stop_cheat_caller_address(admin_address);

// Remove organization as admin
cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite);
dispatcher.remove_organization(org_id);
stop_cheat_caller_address(admin_address);

// Verify event emission
spy
.assert_emitted(
@array![
(
contract_address,
Budget::Budget::Event::OrganizationRemoved(
Budget::Budget::OrganizationRemoved { org_id: org_id },
),
),
],
);
}
Loading