From c7cc9187f39e0068ffbe53215e3c1429a08f0ed2 Mon Sep 17 00:00:00 2001 From: YourGitHubUsername Date: Wed, 26 Feb 2025 20:31:13 +0530 Subject: [PATCH] enh: apply to campaign pool with Unit tests --- src/base/errors.cairo | 3 + src/campaign_pool.cairo | 47 ++++++++- src/interfaces/ICampaignPool.cairo | 4 + tests/test_campaign_pool.cairo | 154 ++++++++++++++++++++++++++++- 4 files changed, 205 insertions(+), 3 deletions(-) diff --git a/src/base/errors.cairo b/src/base/errors.cairo index b376b1a..dcfde76 100644 --- a/src/base/errors.cairo +++ b/src/base/errors.cairo @@ -10,4 +10,7 @@ pub mod Errors { pub const INVALID_CAMPAIGN: felt252 = 'TGN: campaign is not owner!'; pub const INSUFFICIENT_BALANCE: felt252 = 'TGN: insufficient balance!'; pub const TRANSFER_FAILED: felt252 = 'TGN: transfer failed!'; + pub const INVALID_CAMPAIGN_ADDRESS: felt252 = 'TGN: invalid campaign address!'; + pub const INVALID_POOL_ADDRESS: felt252 = 'TGN: invalid pool address!'; + pub const INVALID_AMOUNT: felt252 = 'TGN: invalid amount!'; } diff --git a/src/campaign_pool.cairo b/src/campaign_pool.cairo index 29fbbd7..4001b3d 100644 --- a/src/campaign_pool.cairo +++ b/src/campaign_pool.cairo @@ -11,6 +11,7 @@ mod CampaignPools { syscalls::deploy_syscall, SyscallResultTrait, syscalls, class_hash::class_hash_const, storage::{Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess} }; + use tokengiver::base::errors::Errors; use tokengiver::interfaces::ICampaignPool::ICampaignPool; use tokengiver::base::types::CampaignPool; use openzeppelin::access::ownable::OwnableComponent; @@ -206,7 +207,51 @@ mod CampaignPools { campaign_address: ContractAddress, campaign_pool_address: ContractAddress, amount: u256 - ) {} + ) { + // Get caller address to identify who is applying + let caller = get_caller_address(); + + // Validate that the campaign exists + // We can do this by attempting to read campaign data and asserting it's valid + let campaign_exists = self + .campaign_pool + .read(campaign_address) + .campaign_address != starknet::contract_address_const::<0>(); + assert(campaign_exists, Errors::INVALID_CAMPAIGN_ADDRESS); + + // Validate that the campaign pool exists + let pool_exists = self + .campaign_pool + .read(campaign_pool_address) + .campaign_address != starknet::contract_address_const::<0>(); + assert(pool_exists, Errors::INVALID_POOL_ADDRESS); + + // Ensure amount is valid (not zero) + assert(amount > 0, Errors::INVALID_AMOUNT); + + // Store the application in the mapping + self + .campaign_pool_applications + .write(campaign_address, (campaign_pool_address, amount)); + + // Emit an application event + self + .emit( + ApplicationMade { + campaign_pool_address: campaign_pool_address, + campaign_address: campaign_address, + recipient: caller, + amount: amount, + block_timestamp: get_block_timestamp(), + } + ); + } + fn get_campaign_application( + self: @ContractState, campaign_address: ContractAddress + ) -> (ContractAddress, u256) { + self.campaign_pool_applications.read(campaign_address) + } + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { self.ownable.assert_only_owner(); self.upgradeable.upgrade(new_class_hash); diff --git a/src/interfaces/ICampaignPool.cairo b/src/interfaces/ICampaignPool.cairo index b26480e..f7dd268 100644 --- a/src/interfaces/ICampaignPool.cairo +++ b/src/interfaces/ICampaignPool.cairo @@ -26,4 +26,8 @@ pub trait ICampaignPool { amount: u256 ); fn upgrade(ref self: TState, new_class_hash: ClassHash); + + fn get_campaign_application( + self: @TState, campaign_address: ContractAddress + ) -> (ContractAddress, u256); } diff --git a/tests/test_campaign_pool.cairo b/tests/test_campaign_pool.cairo index 261328b..2244313 100644 --- a/tests/test_campaign_pool.cairo +++ b/tests/test_campaign_pool.cairo @@ -3,6 +3,7 @@ use snforge_std::{ DeclareResultTrait, spy_events, EventSpyAssertionsTrait, get_class_hash }; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ContractAddress, ClassHash, get_block_timestamp}; @@ -13,7 +14,9 @@ use tokengiver::interfaces::ICampaignPool::{ use tokengiver::interfaces::ITokenGiverNft::{ ITokenGiverNftDispatcher, ITokenGiverNftDispatcherTrait }; -use tokengiver::campaign_pool::CampaignPools::{Event, DonationMade, CreateCampaignPool}; +use tokengiver::campaign_pool::CampaignPools::{ + Event, DonationMade, CreateCampaignPool, ApplicationMade +}; use token_bound_accounts::interfaces::ILockable::{ILockableDispatcher, ILockableDispatcherTrait}; fn REGISTRY_HASH() -> felt252 { @@ -130,7 +133,7 @@ fn test_create_campaign_pool() { #[test] -#[fork("Mainnet")] +#[fork("SEPOLIA_LATEST")] fn test_create_campaign_pool_event_emission() { // Get the initial setup let (token_giver_address, _, nft_address) = __setup__(); @@ -172,3 +175,150 @@ fn test_create_campaign_pool_event_emission() { ); // spy.assert_emitted(@array![(token_giver.contract_address, expected_event)]); } +#[test] +#[fork("SEPOLIA_LATEST")] +fn test_apply_to_campaign_pool_success() { + // Set up a campaign pool and a campaign + let (token_giver_address, strk_address, nft_address) = __setup__(); + let token_giver = ICampaignPoolDispatcher { contract_address: token_giver_address }; + let mut spy = spy_events(); + + // Create the required parameters for campaign pool + let registry_hash = REGISTRY_HASH(); + let implementation_hash = IMPLEMENTATION_HASH(); + let salt: felt252 = SALT(); + let recipient: ContractAddress = RECIPIENT(); + let campaign_id: u256 = 10; + let pool_id: u256 = 11; + + // Create campaign pool + start_cheat_caller_address(token_giver_address, recipient); + let campaign_pool_address = token_giver + .create_campaign_pool(registry_hash, implementation_hash, salt, recipient, pool_id); + + // Create campaign with different salt + let campaign_salt: felt252 = 'campaign_salt'; + let campaign_address = token_giver + .create_campaign_pool( + registry_hash, implementation_hash, campaign_salt, recipient, campaign_id + ); + stop_cheat_caller_address(token_giver_address); + + // Amount to request + let amount: u256 = 100; + + // Apply to campaign pool + start_cheat_caller_address(token_giver_address, recipient); + token_giver.apply_to_campaign_pool(campaign_address, campaign_pool_address, amount); + stop_cheat_caller_address(token_giver_address); + + // Verify application was stored correctly + start_cheat_caller_address(token_giver_address, recipient); + let (stored_pool, stored_amount) = token_giver.get_campaign_application(campaign_address); + stop_cheat_caller_address(token_giver_address); + + assert(stored_pool == campaign_pool_address, 'Wrong pool address stored'); + assert(stored_amount == amount, 'Wrong amount stored'); + + // Check event emission + let expected_event = Event::ApplicationMade( + ApplicationMade { + campaign_pool_address: campaign_pool_address, + campaign_address: campaign_address, + recipient: recipient, + amount: amount, + block_timestamp: get_block_timestamp(), + } + ); + + spy.assert_emitted(@array![(token_giver.contract_address, expected_event)]); +} + +#[test] +#[fork("Mainnet")] +#[should_panic(expected: ('TGN: invalid campaign address!',))] +fn test_apply_with_invalid_campaign_address() { + // Set up a campaign pool + let (token_giver_address, _, _) = __setup__(); + let token_giver = ICampaignPoolDispatcher { contract_address: token_giver_address }; + + let registry_hash = REGISTRY_HASH(); + let implementation_hash = IMPLEMENTATION_HASH(); + let salt: felt252 = SALT(); + let recipient: ContractAddress = RECIPIENT(); + let pool_id: u256 = 12; + + // Create campaign pool + start_cheat_caller_address(token_giver_address, recipient); + let campaign_pool_address = token_giver + .create_campaign_pool(registry_hash, implementation_hash, salt, recipient, pool_id); + stop_cheat_caller_address(token_giver_address); + + // Use an invalid campaign address + let invalid_campaign_address: ContractAddress = starknet::contract_address_const::<0x123>(); + let amount: u256 = 100; + + // This should panic with "TGN: invalid campaign address!" + start_cheat_caller_address(token_giver_address, recipient); + token_giver.apply_to_campaign_pool(invalid_campaign_address, campaign_pool_address, amount); + stop_cheat_caller_address(token_giver_address); +} + +#[test] +#[fork("Mainnet")] +#[should_panic(expected: ('TGN: invalid pool address!',))] +fn test_apply_with_invalid_pool_address() { + // Set up a campaign + let (token_giver_address, _, _) = __setup__(); + let token_giver = ICampaignPoolDispatcher { contract_address: token_giver_address }; + + let registry_hash = REGISTRY_HASH(); + let implementation_hash = IMPLEMENTATION_HASH(); + let salt: felt252 = SALT(); + let recipient: ContractAddress = RECIPIENT(); + let campaign_id: u256 = 13; + + // Create campaign + start_cheat_caller_address(token_giver_address, recipient); + let campaign_address = token_giver + .create_campaign_pool(registry_hash, implementation_hash, salt, recipient, campaign_id); + stop_cheat_caller_address(token_giver_address); + + // Use an invalid pool address + let invalid_pool_address: ContractAddress = starknet::contract_address_const::<0x456>(); + let amount: u256 = 100; + + // This should panic with "TGN: invalid pool address!" + start_cheat_caller_address(token_giver_address, recipient); + token_giver.apply_to_campaign_pool(campaign_address, invalid_pool_address, amount); + stop_cheat_caller_address(token_giver_address); +} + +#[test] +#[fork("Mainnet")] +#[should_panic(expected: ('TGN: invalid amount!',))] +fn test_apply_with_zero_amount() { + // Set up a campaign pool and a campaign + let (token_giver_address, _, _) = __setup__(); + let token_giver = ICampaignPoolDispatcher { contract_address: token_giver_address }; + + let registry_hash = REGISTRY_HASH(); + let implementation_hash = IMPLEMENTATION_HASH(); + let salt1: felt252 = 'salt1'; + let salt2: felt252 = 'salt2'; + let recipient: ContractAddress = RECIPIENT(); + let campaign_id: u256 = 14; + let pool_id: u256 = 15; + + // Create campaign pool and campaign + start_cheat_caller_address(token_giver_address, recipient); + let campaign_pool_address = token_giver + .create_campaign_pool(registry_hash, implementation_hash, salt1, recipient, pool_id); + let campaign_address = token_giver + .create_campaign_pool(registry_hash, implementation_hash, salt2, recipient, campaign_id); + + // Try to apply with zero amount, should fail + let zero_amount: u256 = 0; + token_giver.apply_to_campaign_pool(campaign_address, campaign_pool_address, zero_amount); + stop_cheat_caller_address(token_giver_address); +}