Skip to content

Latest commit

 

History

History
219 lines (171 loc) · 6.48 KB

File metadata and controls

219 lines (171 loc) · 6.48 KB

Auto-Claim Feature Implementation

Overview

Implemented automatic fund transfer when campaign donation targets are reached. This streamlines the beneficiary experience by eliminating manual claim steps while maintaining security and consistency.

Changes Made

1. New Event Structure

File: contracts/stellar-give/src/lib.rs

Added AutoClaimedEvent struct (lines ~63-69):

#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct AutoClaimedEvent {
    pub campaign_id: u64,
    pub total_raised: i128,
    pub beneficiary: Address,
}

Purpose: Emitted when a donation automatically triggers fund distribution to the first beneficiary.


2. Reusable Fund Distribution Helper

File: contracts/stellar-give/src/lib.rs

Added distribute_funds() helper function (lines ~458-508):

fn distribute_funds(
    env: &Env,
    admin: &Address,
    campaign: &Campaign,
    amount: i128,
) -> Result<(), ContractError>

Features:

  • Calculates platform fee (1% via calculate_platform_fee())
  • Transfers fee to platform admin (skipped if fee rounds to zero)
  • Distributes net proceeds proportionally to beneficiaries by basis-point shares
  • First beneficiary absorbs rounding dust to ensure exact balance: fee + Σpayouts == amount

Benefits:

  • DRY principle: reused by both donate() and claim_funds()
  • Consistent fee calculation and distribution logic
  • Reduced code duplication

3. Enhanced Donate Function

File: contracts/stellar-give/src/lib.rs

Modified donate() to auto-claim when target is reached (lines ~725-764):

Workflow:

  1. Accept donation and update raised_amount
  2. Check if raised_amount >= target_amount
  3. If target reached:
    • Emit GoalReachedEvent
    • Call distribute_funds() to transfer funds to beneficiaries
    • Set campaign.status = CampaignStatus::Claimed
    • Clear campaign.raised_amount = 0
    • Emit AutoClaimedEvent with first beneficiary address
  4. Emit standard DonationEvent
  5. Return early exit via reentrancy lock

Key Points:

  • Gas-efficient: all operations within single donation transaction
  • Reentrancy-protected via existing enter_lock()/exit_lock() mechanism
  • Subsequent donations rejected immediately (campaign no longer Active)
  • Handles overshoot: if donation exceeds target, entire raised amount is distributed

4. Refactored Claim Funds

File: contracts/stellar-give/src/lib.rs

Simplified claim_funds() (lines ~870-883) to use distribute_funds():

  • Removed 60 lines of duplicated distribution logic
  • Calls distribute_funds() once with admin and campaign
  • Maintains backward compatibility
  • Same event emission as before

Test Coverage

Added 5 comprehensive tests (lines ~3828-3992):

1. donate_at_target_triggers_auto_claim()

  • Verifies donation exactly meeting target auto-claims
  • Checks campaign status changes to Claimed
  • Confirms beneficiary receives correct net amount

2. donate_after_auto_claim_is_rejected()

  • Critical security test: verifies subsequent donations fail
  • Confirms CampaignNotActive error returned
  • Prevents donation post-claim exploitation

3. donate_auto_claim_emits_auto_claimed_event()

  • Verifies AutoClaimedEvent is emitted with correct payload
  • Checks campaign_id, total_raised, and beneficiary address
  • Confirms indexers can track auto-claim triggers

4. donate_over_target_triggers_auto_claim()

  • Tests donation exceeding target
  • Confirms entire raised amount distributed (not just target)
  • Validates overshoot handling

5. auto_claim_with_multiple_beneficiaries()

  • Tests complex benefit-sharing scenario (3 beneficiaries, 50/30/20 split)
  • Verifies proportional distribution with rounding
  • First beneficiary receives remainder to ensure Σpayouts == net_proceeds

Gas Efficiency & Limits

Current Approach

  • Single transaction: donation + auto-claim in one atomic operation
  • Lock protection: reentrancy detection prevents recursive calls
  • Efficient distribution: O(n) complexity where n = number of beneficiaries (typically 1-3)

No Additional Constraints

  • Existing Soroban gas limits apply
  • Distribution algorithm uses same pattern as manual claim_funds()
  • No new storage operations beyond campaign status update

Event Flow Diagram

User Donation
    ↓
[Reentrancy Lock Enter]
    ↓
Transfer Tokens to Contract
    ↓
Update raised_amount
    ↓
Check: raised_amount >= target?
    ├─ NO → Emit GoalReachedEvent (if new goal hit)
    │        Emit DonationEvent
    │        Exit Lock → Success
    │
    └─ YES → Emit GoalReachedEvent
            Call distribute_funds()
            Set status = Claimed
            Clear raised_amount = 0
            Emit AutoClaimedEvent ← NEW
            Emit DonationEvent
            Exit Lock → Success

Future donations to campaign:
    ↓
Check: status == Active? → NO
    ↓
Reject: CampaignNotActive

Backward Compatibility

Fully compatible:

  • claim_funds() behavior unchanged
  • Existing campaigns continue to work
  • Event structure additions don't break indexers
  • Campaign status machine unchanged

Security Considerations

  1. Reentrancy: Protected by existing lock mechanism
  2. Authorization: No new auth checks needed (donate already requires auth)
  3. Integrity: Fund distribution uses same validated logic as claim_funds()
  4. Atomicity: All-or-nothing via Soroban transaction semantics
  5. State consistency: Campaign marked Claimed, raised_amount zeroed, preventing double-claims

Usage Example

// Campaign with 10M stroops target
let campaign_id = client.create_campaign(
    &creator,
    &vec![(beneficiary, 10_000)],
    &String::from_str(&env, "Relief Fund"),
    &String::from_str(&env, "https://..."),
    &symbol_short!("relief"),
    &10_000_000,  // target
    &deadline,
    &token_address,
    &None,
);

// Single donation hits target
client.donate(&donor, &campaign_id, &10_000_000, &false, &None);

// Result: 
// - Campaign auto-claims
// - Beneficiary receives 9.9M (after 1% fee)
// - Subsequent donations rejected
// - Indexers see AutoClaimedEvent

Migration Notes

No migration needed. New feature activates on deployment:

  • Existing claimed campaigns unaffected
  • Existing active campaigns gain auto-claim on next donation hitting target
  • No state transformation required