Skip to content
Merged
3 changes: 3 additions & 0 deletions src/base/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ pub mod Errors {
// Throw Error when Insufficient balance
pub const INSUFFICIENT_BALANCE: felt252 = 'Error: Insufficient balance';

// Throw Error when donation token is invalid
pub const INVALID_DONATION_TOKEN: felt252 = 'Error: Invalid donation token';

// Throw Error when Fee percent exceeds 100%
pub const PROTOCOL_FEE_PERCENTAGE_EXCEED: felt252 = 'Error: Fee percent exceeds 100%';
}
Expand Down
24 changes: 16 additions & 8 deletions src/campaign_donation.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub mod CampaignDonation {
CALLER_NOT_CAMPAIGN_OWNER, CALLER_NOT_DONOR, CAMPAIGN_CLOSED, CAMPAIGN_HAS_DONATIONS,
CAMPAIGN_NOT_CANCELLED, CAMPAIGN_NOT_CLOSED, CAMPAIGN_NOT_FOUND, CAMPAIGN_REF_EMPTY,
CAMPAIGN_REF_EXISTS, CAMPAIGN_WITHDRAWN, CANNOT_DENOTE_ZERO_AMOUNT, DONATION_NOT_FOUND,
DOUBLE_WITHDRAWAL, INSUFFICIENT_ALLOWANCE, INSUFFICIENT_BALANCE, MORE_THAN_TARGET,
NFT_NOT_CONFIGURED, OPERATION_OVERFLOW, PROTOCOL_FEE_ADDRESS_NOT_SET,
DOUBLE_WITHDRAWAL, INSUFFICIENT_ALLOWANCE, INSUFFICIENT_BALANCE, INVALID_DONATION_TOKEN,
MORE_THAN_TARGET, NFT_NOT_CONFIGURED, OPERATION_OVERFLOW, PROTOCOL_FEE_ADDRESS_NOT_SET,
PROTOCOL_FEE_PERCENTAGE_EXCEED, REFUND_ALREADY_CLAIMED, TARGET_NOT_REACHED, TARGET_REACHED,
WITHDRAWAL_FAILED, ZERO_ALLOWANCE, ZERO_AMOUNT,
};
Expand Down Expand Up @@ -164,13 +164,17 @@ pub mod CampaignDonation {
#[abi(embed_v0)]
impl CampaignDonationImpl of ICampaignDonation<ContractState> {
fn create_campaign(
ref self: ContractState, campaign_ref: felt252, target_amount: u256,
ref self: ContractState,
campaign_ref: felt252,
target_amount: u256,
donation_token: ContractAddress,
) -> u256 {
let caller = get_caller_address();
let timestamp = get_block_timestamp();
let ref_campaign = campaign_ref.clone();
let campaign_target_amount = target_amount.clone();
let campaign_id = self._create_campaign(ref_campaign, campaign_target_amount);
let campaign_id = self
._create_campaign(ref_campaign, campaign_target_amount, donation_token);
self
.emit(
Event::Campaign(
Expand Down Expand Up @@ -463,11 +467,15 @@ pub mod CampaignDonation {
#[generate_trait]
impl InternalImpl of InternalTrait {
fn _create_campaign(
ref self: ContractState, campaign_ref: felt252, target_amount: u256,
ref self: ContractState,
campaign_ref: felt252,
target_amount: u256,
donation_token: ContractAddress,
) -> u256 {
assert(campaign_ref != '', CAMPAIGN_REF_EMPTY);
assert(!self.campaign_refs.read(campaign_ref), CAMPAIGN_REF_EXISTS);
assert(target_amount > 0, ZERO_AMOUNT);
assert(!donation_token.is_zero(), INVALID_DONATION_TOKEN);
let campaign_id: u256 = self.campaign_counts.read() + 1;
let caller = get_caller_address();
let current_balance: u256 = 0;
Expand All @@ -481,7 +489,7 @@ pub mod CampaignDonation {
campaign_reference: campaign_ref,
is_closed: false,
is_goal_reached: false,
donation_token: self.donation_token.read(),
donation_token: donation_token,
is_cancelled: false,
};

Expand All @@ -497,7 +505,7 @@ pub mod CampaignDonation {
let mut campaign = self.get_campaign(campaign_id);
let contract_address = get_contract_address();

let donation_token = self.donation_token.read();
let donation_token = campaign.donation_token;
// cannot send more than target amount
assert!(amount <= campaign.target_amount, "More than Target");

Expand Down Expand Up @@ -560,7 +568,7 @@ pub mod CampaignDonation {

assert(!self.campaign_withdrawn.read(campaign_id), DOUBLE_WITHDRAWAL);

let donation_token = self.donation_token.read();
let donation_token = campaign.donation_token;

let token_dispatcher = IERC20Dispatcher { contract_address: donation_token };

Expand Down
6 changes: 5 additions & 1 deletion src/interfaces/ICampaignDonation.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub trait ICampaignDonation<TContractState> {
/// # Arguments
/// * `campaign_ref` - A unique 5-character identifier for the campaign
/// * `target_amount` - The fundraising goal amount in the donation token
/// * `donation_token` - The address of the donation token
///
/// # Returns
/// * `u256` - The newly created campaign's ID
Expand All @@ -26,7 +27,10 @@ pub trait ICampaignDonation<TContractState> {
/// * If `campaign_ref` already exists
/// * If `target_amount` is zero
fn create_campaign(
ref self: TContractState, campaign_ref: felt252, target_amount: u256,
ref self: TContractState,
campaign_ref: felt252,
target_amount: u256,
donation_token: ContractAddress,
) -> u256;
Comment on lines 29 to 34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify consistent implementation of the new donation_token parameter.

The interface change correctly adds support for per-campaign donation tokens. Ensure that all implementations and calls to this method are updated to include the new parameter.


🏁 Script executed:

#!/bin/bash
# Description: Verify all implementations and calls to create_campaign include the donation_token parameter

# Search for create_campaign implementations
ast-grep --pattern $'fn create_campaign($$$) -> $_ {
  $$$
}'

# Search for create_campaign method calls
rg -A 5 "create_campaign\(" --type cairo

Length of output: 135


🏁 Script executed:

#!/bin/bash
# Locate the ICampaignDonation interface file
INTERFACE_FILE=$(fd ICampaignDonation.cairo)

# Show the interface and its method signature
echo "== Interface: $INTERFACE_FILE =="
sed -n '1,200p' "$INTERFACE_FILE"

# Search for any impl blocks of this interface
echo "== Implementations of ICampaignDonation =="
rg -n "impl ICampaignDonation" --glob "*.cairo" || echo "No impl blocks found"

# Search for all create_campaign definitions (to catch overrides)
echo "== Definitions of fn create_campaign =="
rg -n "fn create_campaign" --glob "*.cairo" || echo "No fn create_campaign definitions found"

# Search for all calls to create_campaign(
echo "== Calls to create_campaign( =="
rg -n "create_campaign\(" --glob "*.cairo" || echo "No calls to create_campaign found"

Length of output: 13621


Fix missing donation_token in test calls

The interface and contract implementation already include the new donation_token parameter. However, there’s at least one test invoking create_campaign with only two arguments:

• tests/test_campaign_donation.cairo:1211

- let campaign_id = campaign_donation.create_campaign('Test', target_amount);
+ let campaign_id = campaign_donation.create_campaign('Test', target_amount, donation_token);

Please update this call (and any others missing the third argument) to include donation_token so tests compile and run as expected.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn create_campaign(
ref self: TContractState, campaign_ref: felt252, target_amount: u256,
ref self: TContractState,
campaign_ref: felt252,
target_amount: u256,
donation_token: ContractAddress,
) -> u256;
- let campaign_id = campaign_donation.create_campaign('Test', target_amount);
+ let campaign_id = campaign_donation.create_campaign('Test', target_amount, donation_token);
🤖 Prompt for AI Agents
In src/interfaces/ICampaignDonation.cairo around lines 29 to 34, the
create_campaign function signature includes a new donation_token parameter, but
test calls such as in tests/test_campaign_donation.cairo at line 1211 are
missing this argument. Update all calls to create_campaign in the test files to
pass the donation_token argument as the third parameter to match the updated
interface and ensure tests compile and run correctly.


/// Makes a donation to a specific campaign
Expand Down
17 changes: 9 additions & 8 deletions src/payment_stream.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ pub mod PaymentStream {
use crate::base::errors::Errors::{
DECIMALS_TOO_HIGH, FEE_TOO_HIGH, INSUFFICIENT_ALLOWANCE, INSUFFICIENT_AMOUNT,
INVALID_RECIPIENT, INVALID_TOKEN, NON_TRANSFERABLE_STREAM, ONLY_NFT_OWNER_CAN_DELEGATE,
SAME_COLLECTOR_ADDRESS, SAME_OWNER, STREAM_CANCELED, STREAM_HAS_DELEGATE, STREAM_NOT_ACTIVE,
STREAM_NOT_PAUSED, TOO_SHORT_DURATION, UNEXISTING_STREAM, WRONG_RECIPIENT,
WRONG_RECIPIENT_OR_DELEGATE, WRONG_SENDER, ZERO_AMOUNT, OVERDEPOSIT
OVERDEPOSIT, SAME_COLLECTOR_ADDRESS, SAME_OWNER, STREAM_CANCELED, STREAM_HAS_DELEGATE,
STREAM_NOT_ACTIVE, STREAM_NOT_PAUSED, TOO_SHORT_DURATION, UNEXISTING_STREAM,
WRONG_RECIPIENT, WRONG_RECIPIENT_OR_DELEGATE, WRONG_SENDER, ZERO_AMOUNT,
};
use crate::base::types::{ProtocolMetrics, Stream, StreamMetrics, StreamStatus};

Expand Down Expand Up @@ -353,7 +353,6 @@ pub mod PaymentStream {
// Check: stream is not canceled
assert(stream.status != StreamStatus::Canceled, STREAM_CANCELED);


let token_address = stream.token;

// Effect: update the stream balance by adding the deposit amount
Expand Down Expand Up @@ -464,7 +463,7 @@ pub mod PaymentStream {
} else {
// For active streams, the withdrawable amount is the minimum of stream balance and
// total debt
total_debt - stream.withdrawn_amount
total_debt - stream.withdrawn_amount
}
}

Expand Down Expand Up @@ -673,7 +672,6 @@ pub mod PaymentStream {

#[abi(embed_v0)]
impl PaymentStreamImpl of IPaymentStream<ContractState> {

/// @notice Creates a new stream and funds it with tokens in a single transaction
/// @dev Combines the create_stream and deposit functions into one efficient operation
fn create_stream(
Expand Down Expand Up @@ -806,7 +804,10 @@ pub mod PaymentStream {

self.accesscontrol.revoke_role(PROTOCOL_OWNER_ROLE, current_owner);
self.accesscontrol._grant_role(PROTOCOL_OWNER_ROLE, new_protocol_owner);
self.emit(ProtocolOwnerUpdated { new_protocol_owner, old_protocol_owner: current_owner });
self
.emit(
ProtocolOwnerUpdated { new_protocol_owner, old_protocol_owner: current_owner },
);
}

fn get_fee_collector(self: @ContractState) -> ContractAddress {
Expand Down Expand Up @@ -911,7 +912,7 @@ pub mod PaymentStream {

// Pay the recipient the remaining balance
let recipient = stream.recipient;

// Update Stream in State
self.streams.write(stream_id, stream);
let amount_due = self._withdrawable_amount(stream_id);
Expand Down
Loading
Loading