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
2 changes: 2 additions & 0 deletions src/base/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ pub const ERROR_MILESTONE_NOT_COMPLETED: felt252 = 'Milestone not completed';
pub const ERROR_UNAUTHORIZED_REQUESTER: felt252 = 'Only project owner can request';
pub const ERROR_CONTRACT_PAUSED: felt252 = 'Contract is paused';
pub const ERROR_ALREADY_PAUSED: felt252 = 'Contract already paused';
pub const ERROR_INVALID_MILESTONE_DESCRIPTION: felt252 = 'Invalid milestone description';
pub const ERROR_INVALID_BUDGET: felt252 = 'Invalid budget';
19 changes: 17 additions & 2 deletions src/budgetchain/Budget.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ pub mod Budget {

#[derive(Drop, starknet::Event)]
pub struct MilestoneCreated {
pub organization: u256,
pub organization: ContractAddress,
pub project_id: u64,
pub milestone_description: felt252,
pub milestone_amount: u256,
Expand Down Expand Up @@ -407,6 +407,10 @@ pub mod Budget {
milestone_descriptions: Array<felt252>,
milestone_amounts: Array<u256>,
) -> u64 {
assert(project_owner != contract_address_const::<0>(), ERROR_ZERO_ADDRESS);
assert(milestone_descriptions.len() > 0, ERROR_INVALID_MILESTONE_DESCRIPTION);
assert(milestone_amounts.len() > 0, ERROR_INVALID_MILESTONE_DESCRIPTION);
assert(total_budget > 0, ERROR_INVALID_BUDGET);
// Ensure the contract is not paused
self.assert_not_paused();

Expand All @@ -426,7 +430,7 @@ pub mod Budget {
};
assert(sum == total_budget, ERROR_BUDGET_MISMATCH);

let project_id = self.project_count.read();
let project_id = self.project_count.read() + 1;

let new_project = Project {
id: project_id, org: org, owner: project_owner, total_budget: total_budget,
Expand All @@ -450,9 +454,20 @@ pub mod Budget {
released: false,
},
);
self
.emit(
MilestoneCreated {
organization: org,
project_id: project_id,
milestone_description: *milestone_descriptions.at(j),
milestone_amount: *milestone_amounts.at(j),
created_at: get_block_timestamp(),
},
);
j += 1;
};

self.project_owners.write(project_id, project_owner);
self.project_count.write(project_id + 1);
self.org_milestones.write(org, milestone_count.try_into().unwrap());

Expand Down
273 changes: 266 additions & 7 deletions tests/test_budgetchain.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use budgetchain_contracts::interfaces::IBudget::{IBudgetDispatcher, IBudgetDispa
use snforge_std::{
CheatSpan, ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait,
cheat_caller_address, declare, spy_events, start_cheat_caller_address,
stop_cheat_caller_address,
stop_cheat_caller_address, start_cheat_block_timestamp,
};
use starknet::{ContractAddress, contract_address_const};
use starknet::{ContractAddress, contract_address_const, get_block_timestamp};

fn setup() -> (ContractAddress, ContractAddress) {
let admin_address: ContractAddress = contract_address_const::<'admin'>();
Expand Down Expand Up @@ -948,7 +948,8 @@ fn test_get_project_remaining_budget_multiple_releases() {
}

#[test]
fn test_get_project_remaining_budget_zero_funding() {
#[should_panic(expected: 'Milestone sum != total budget')]
fn test_get_project_invalid_total_budget() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };
Expand All @@ -967,11 +968,13 @@ fn test_get_project_remaining_budget_zero_funding() {
stop_cheat_caller_address(admin_address);

// Create project with zero budget
let total_budget: u256 = 0;
let total_budget: u256 = 80;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array!['Empty Milestone'];
let mut milestone_amounts = array![total_budget];
let mut milestone_descriptions = array![
'Empty Milestone', 'non empty milestone', 'something else',
];
let mut milestone_amounts = array![25, 59, 30];

// Set org_address as caller to create project
cheat_caller_address(contract_address, org_address, CheatSpan::Indefinite);
Expand All @@ -986,6 +989,47 @@ fn test_get_project_remaining_budget_zero_funding() {
assert(remaining_budget == 0, 'Zero budget incorrect');
}

#[test]
fn test_get_project_remaining_budget_zero_funding() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

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

// Create project owner
let project_owner = contract_address_const::<'ProjectOwner'>();

// 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);

// Create project with zero budget
let total_budget: u256 = 114;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array![
'Empty Milestone', 'non empty milestone', 'something else',
];
let mut milestone_amounts = array![25, 59, 30];

// Set org_address as caller to create project
cheat_caller_address(contract_address, org_address, CheatSpan::Indefinite);
let project_id = dispatcher
.allocate_project_budget(
org_address, project_owner, total_budget, milestone_descriptions, milestone_amounts,
);
stop_cheat_caller_address(org_address);

// Test that remaining budget is zero
let remaining_budget = dispatcher.get_project_remaining_budget(project_id);
assert(remaining_budget == 114, 'Zero budget incorrect');
}

#[test]
fn test_get_project_remaining_budget_full_utilization() {
// Setup project with milestones
Expand Down Expand Up @@ -1221,7 +1265,7 @@ fn test_functions_should_panic_when_contract_is_done() {
stop_cheat_caller_address(admin_address);

// Create project with zero budget
let total_budget: u256 = 0;
let total_budget: u256 = 20;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array!['Empty Milestone'];
Expand Down Expand Up @@ -1287,6 +1331,7 @@ fn test_get_project_budget_full_utilization() {
}

#[test]
#[should_panic(expected: 'Invalid budget')]
fn test_get_project_budget_zero_funding() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
Expand Down Expand Up @@ -1721,3 +1766,217 @@ fn test_remove_organization_event_emission() {
],
);
}

#[test]
#[should_panic(expected: 'Zero address forbidden')]
fn test_allocate_project_budget_zero_address() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

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

// Create project owner
let project_owner = contract_address_const::<0>();

// 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);

// Create project with zero budget
let total_budget: u256 = 40;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array!['Empty Milestone'];
let mut milestone_amounts = array![total_budget];

// Set org_address as caller to create project
cheat_caller_address(contract_address, org_address, CheatSpan::Indefinite);
dispatcher
.allocate_project_budget(
org_address, project_owner, total_budget, milestone_descriptions, milestone_amounts,
);
stop_cheat_caller_address(org_address);
}

#[test]
#[should_panic(expected: 'Invalid milestone description')]
fn test_allocate_project_budget_zero_milestone_description() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

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

// Create project owner
let project_owner = contract_address_const::<'owner'>();

// 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);

// Create project with zero budget
let total_budget: u256 = 0;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array![];
let mut milestone_amounts = array![total_budget];

// Set org_address as caller to create project
cheat_caller_address(contract_address, org_address, CheatSpan::Indefinite);
dispatcher
.allocate_project_budget(
org_address, project_owner, total_budget, milestone_descriptions, milestone_amounts,
);
stop_cheat_caller_address(org_address);
}

#[test]
#[should_panic(expected: 'Invalid milestone description')]
fn test_allocate_project_budget_zero_milestone_amount() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

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

// Create project owner
let project_owner = contract_address_const::<'owner'>();

// 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);

// Create project with zero budget
let total_budget: u256 = 60;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array!['milestone description'];
let mut milestone_amounts = array![];

// Set org_address as caller to create project
cheat_caller_address(contract_address, org_address, CheatSpan::Indefinite);
dispatcher
.allocate_project_budget(
org_address, project_owner, total_budget, milestone_descriptions, milestone_amounts,
);
stop_cheat_caller_address(org_address);
}

#[test]
#[should_panic(expected: 'Invalid budget')]
fn test_allocate_project_budget_Invalid_budget() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

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

// Create project owner
let project_owner = contract_address_const::<'owner'>();

// 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);

// Create project with zero budget
let total_budget: u256 = 0;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array!['milestone description'];
let mut milestone_amounts = array![total_budget, 54, 56];

// Set org_address as caller to create project
cheat_caller_address(contract_address, org_address, CheatSpan::Indefinite);
dispatcher
.allocate_project_budget(
org_address, project_owner, total_budget, milestone_descriptions, milestone_amounts,
);
stop_cheat_caller_address(org_address);
}

#[test]
fn test_allocate_project_event() {
// Setup project with no budget
let (contract_address, admin_address) = setup();
let dispatcher = IBudgetDispatcher { contract_address };

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

// Create project owner
let project_owner = contract_address_const::<'owner'>();

// 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);

// Create project with budget
let total_budget: u256 = 120;

// Set milestone descriptions and amounts
let mut milestone_descriptions = array![
'milestone description', 'non milestone description', 'yeah',
];
let mut milestone_amounts = array![10, 54, 56];

// Set block timestamp to a known value (default is 0)
// You're not currently setting this in your test, so get_block_timestamp() might be returning 0
start_cheat_block_timestamp(contract_address, 500);

// Start spy BEFORE calling functions that emit events
let mut spy = spy_events();

// Set org_address as caller to create project
cheat_caller_address(contract_address, org_address, CheatSpan::Indefinite);

let project_id = dispatcher
.allocate_project_budget(
org_address,
project_owner,
total_budget,
milestone_descriptions.clone(),
milestone_amounts.clone(),
);

// Check for MilestoneCreated event with correct timestamp (500)
spy
.assert_emitted(
@array![
(
contract_address,
Budget::Budget::Event::MilestoneCreated(
Budget::Budget::MilestoneCreated {
organization: org_address,
project_id: project_id,
milestone_description: *milestone_descriptions.at(0),
milestone_amount: *milestone_amounts.at(0),
created_at: 500 // Must match the set timestamp
},
),
),
],
);

stop_cheat_caller_address(org_address);
}

Loading