Skip to content

[MEDIUM] DonorRecord::apply_donation (and new_for) is dead code — donate() mutates the record inline #39

@Alqku

Description

@Alqku

Overview

DonorRecord::apply_donation is a public method on DonorRecord (campaign/src/types.rs line 451) that maintains the donor's running totals (total_donated, donation_count, last donation metadata). It is defined, public, exported, and has a multi-line doc comment — but the canonical CampaignContract::donate entry point (campaign/src/lib.rs lines 134–249) never calls it. Instead, donate() mutates the donor record inline with explicit field writes that mirror the body of apply_donation almost exactly. Two divergent paths for the same operation will silently desynchronise: a future bug fix to one is unlikely to be ported to the other.

Evidence

// campaign/src/lib.rs (donate, ~line 200)
let mut donor_record = existing_donor.unwrap_or(DonorRecord { /* zero values */ });
donor_record.total_donated = donor_record.total_donated
    .checked_add(amount)
    .unwrap_or_else(|| panic_with_error(&env, Error::Overflow));
donor_record.asset = asset.clone();
donor_record.last_donation_time = env.ledger().timestamp();
donor_record.last_donation_ledger = env.ledger().sequence();
donor_record.donation_count = donor_record.donation_count.saturating_add(1);
set_donor(&env, &donor, &donor_record);

vs. the never-called helper:

// campaign/src/types.rs
impl DonorRecord {
    pub fn new_for(donor: Address, asset: AssetInfo) -> Self { ... }
    pub fn apply_donation(&mut self, amount: i128, time: u64, ledger: u32, asset: AssetInfo) { ... }
}

new_for is also defined but unused; only apply_donation is invoked (zero times, per cargo expand-equivalent grep).

Impact

Recommended Approach

Pick one:

Option A — delete the dead helpers. Remove DonorRecord::apply_donation and DonorRecord::new_for from types.rs and centralise the mutation in donate(). Pro: smallest diff. Con: removes the only "named" abstraction.

Option B — wire them in. Replace the inline block in donate() with:

let mut donor_record = existing_donor.unwrap_or_else(|| DonorRecord::new_for(donor.clone(), asset.clone()));
donor_record.apply_donation(&env, amount, env.ledger().timestamp(), env.ledger().sequence(), asset.clone());
set_donor(&env, &donor, &donor_record);

…and at the same time resolve #37 so apply_donation uses checked arithmetic.

Option B is recommended because it gives the inline block a name and forces any future invariant change to live in one place.

Acceptance Criteria

  • DonorRecord::apply_donation is either wired into donate() (Option B) or removed (Option A)
  • If wired in, the call site is the only place that mutates a DonorRecord post-unwrap_or/new_for
  • If removed, no remaining references in campaign/, crates/contracts/, crates/tools/ or token-bridge/
  • No behaviour change for existing donors — every prior test still passes

Affected Files

  • campaign/src/lib.rs
  • campaign/src/types.rs

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official Campaigngood first issueGood for newcomersrefactoringCode restructuring without behavioral change

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions