From 92e2efd9a2ecf4c4d640f2b007a3713399b0fb31 Mon Sep 17 00:00:00 2001 From: enyinnaya1234 Date: Sat, 3 May 2025 09:14:05 +0100 Subject: [PATCH 1/3] fix validation --- src/base/errors.cairo | 2 + src/budgetchain/Budget.cairo | 19 +++- tests/test_budgetchain.cairo | 205 +++++++++++++++++++++++++++++++++-- 3 files changed, 217 insertions(+), 9 deletions(-) diff --git a/src/base/errors.cairo b/src/base/errors.cairo index 2518d4f..b480de1 100644 --- a/src/base/errors.cairo +++ b/src/base/errors.cairo @@ -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'; diff --git a/src/budgetchain/Budget.cairo b/src/budgetchain/Budget.cairo index 6302979..46ff20f 100644 --- a/src/budgetchain/Budget.cairo +++ b/src/budgetchain/Budget.cairo @@ -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, @@ -407,6 +407,10 @@ pub mod Budget { milestone_descriptions: Array, milestone_amounts: Array, ) -> 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(); @@ -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, @@ -453,8 +457,19 @@ pub mod Budget { 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()); + self + .emit( + MilestoneCreated { + organization: org, + project_id: project_id, + milestone_description: *milestone_descriptions.at(0), + milestone_amount: *milestone_amounts.at(0), + created_at: get_block_timestamp(), + } + ); // Emit event self.emit(ProjectAllocated { project_id, org, project_owner, total_budget }); diff --git a/tests/test_budgetchain.cairo b/tests/test_budgetchain.cairo index 669ec3b..79fce68 100644 --- a/tests/test_budgetchain.cairo +++ b/tests/test_budgetchain.cairo @@ -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'>(); @@ -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 }; @@ -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); @@ -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 @@ -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']; @@ -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(); @@ -1721,3 +1766,149 @@ 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); +} + + From e34084539d12df94af68ee8f64611a08b6b8e229 Mon Sep 17 00:00:00 2001 From: enyinnaya1234 Date: Sat, 3 May 2025 09:58:10 +0100 Subject: [PATCH 2/3] updated --- src/budgetchain/Budget.cairo | 22 ++++++------ tests/test_budgetchain.cairo | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/src/budgetchain/Budget.cairo b/src/budgetchain/Budget.cairo index 46ff20f..50eed68 100644 --- a/src/budgetchain/Budget.cairo +++ b/src/budgetchain/Budget.cairo @@ -454,22 +454,24 @@ 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()); - self - .emit( - MilestoneCreated { - organization: org, - project_id: project_id, - milestone_description: *milestone_descriptions.at(0), - milestone_amount: *milestone_amounts.at(0), - created_at: get_block_timestamp(), - } - ); + // Emit event self.emit(ProjectAllocated { project_id, org, project_owner, total_budget }); diff --git a/tests/test_budgetchain.cairo b/tests/test_budgetchain.cairo index 79fce68..0135d1e 100644 --- a/tests/test_budgetchain.cairo +++ b/tests/test_budgetchain.cairo @@ -1911,4 +1911,71 @@ fn test_allocate_project_budget_Invalid_budget() { 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); +} From 5d9340b52921133c1bef2f68a5082a6ea25bfb26 Mon Sep 17 00:00:00 2001 From: enyinnaya1234 Date: Sat, 3 May 2025 10:00:04 +0100 Subject: [PATCH 3/3] formatted with scarb --- src/budgetchain/Budget.cairo | 6 ++-- tests/test_budgetchain.cairo | 53 ++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/budgetchain/Budget.cairo b/src/budgetchain/Budget.cairo index 50eed68..11ccc27 100644 --- a/src/budgetchain/Budget.cairo +++ b/src/budgetchain/Budget.cairo @@ -454,7 +454,7 @@ pub mod Budget { released: false, }, ); - self + self .emit( MilestoneCreated { organization: org, @@ -462,16 +462,14 @@ pub mod Budget { 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()); - // Emit event self.emit(ProjectAllocated { project_id, org, project_owner, total_budget }); diff --git a/tests/test_budgetchain.cairo b/tests/test_budgetchain.cairo index 0135d1e..1ee7620 100644 --- a/tests/test_budgetchain.cairo +++ b/tests/test_budgetchain.cairo @@ -1916,39 +1916,39 @@ 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, @@ -1957,25 +1957,26 @@ fn test_allocate_project_event() { 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 - }, + 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); }