Skip to content

[Contract] Implement Auto-Refund Fallback Mechanism on Expired Campaigns - #562

Open
Skinny001 wants to merge 3 commits into
Fundable-Protocol:mainfrom
Skinny001:feat/campaign-funding-contract
Open

[Contract] Implement Auto-Refund Fallback Mechanism on Expired Campaigns#562
Skinny001 wants to merge 3 commits into
Fundable-Protocol:mainfrom
Skinny001:feat/campaign-funding-contract

Conversation

@Skinny001

@Skinny001 Skinny001 commented Jul 29, 2026

Copy link
Copy Markdown

This pull request introduces a new smart contract for campaign-based crowdfunding on Soroban, adds it to the workspace, and provides initial test coverage for key error conditions. The main focus is on creating, funding, claiming, and refunding campaigns, with robust error handling and event emission for transparency.
close #518
New Campaign Funding Contract

  • Added a new campaign-funding contract implementing crowdfunding logic, including campaign creation, contribution, claim, and refund functions, with comprehensive error handling and event emission. (contracts/campaign-funding/src/lib.rs)
  • Created the Cargo.toml for the new contract, specifying dependencies and build configuration. (contracts/campaign-funding/Cargo.toml)
  • Registered the new contract in the workspace so it is built and tested with the rest of the project. (contracts/Cargo.toml)

Testing and Snapshots

  • Added test snapshots to verify error handling for cases such as contributing to a non-existent campaign, creating a campaign with a deadline in the past, and creating a campaign with a zero goal. (contracts/campaign-funding/test_snapshots/test/test/test_contribute_campaign_not_found.1.json, test_create_campaign_deadline_in_past.1.json, test_create_campaign_zero_goal.1.json) [1] [2] [3]…und triggers

New Soroban smart contract for campaign funding with:

  • Campaign creation with goal amount and deadline
  • Permissionless contributions to active campaigns
  • Creator claim when goal met after deadline
  • Auto-refund for backers when deadline passes without meeting goal
  • Permissionless refund triggering (anyone can refund any backer)
  • 27 comprehensive unit tests covering success, failure, and edge cases
  • Storage TTL management following existing contract patterns

Summary by CodeRabbit

  • New Features
    • Added campaign-based token funding with configurable goals and deadlines, plus status queries.
    • Refunds for campaigns that miss their goal now also expire the campaign and emit a campaign_expired event.
    • Campaign creation now rejects deadlines that are already in the past.
  • Bug Fixes
    • Expiration behavior is enforced consistently after refunding an unsuccessful campaign.
  • Tests
    • Expanded unit tests and ledger snapshots for full lifecycle coverage, including post-refund “Expired” behavior and additional event assertions.

…und triggers

New Soroban smart contract for campaign funding with:
- Campaign creation with goal amount and deadline
- Permissionless contributions to active campaigns
- Creator claim when goal met after deadline
- Auto-refund for backers when deadline passes without meeting goal
- Permissionless refund triggering (anyone can refund any backer)
- 27 comprehensive unit tests covering success, failure, and edge cases
- Storage TTL management following existing contract patterns
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@Skinny001 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Skinny001, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 42c752b0-07e1-456d-af23-ae1a5f2fd1ea

📥 Commits

Reviewing files that changed from the base of the PR and between 0b1ca7c and 4804af0.

📒 Files selected for processing (3)
  • contracts/campaign-funding/src/lib.rs
  • contracts/campaign-funding/src/test.rs
  • contracts/campaign-funding/test_snapshots/test/test/test_create_campaign_deadline_too_far.1.json
📝 Walkthrough

Walkthrough

Adds the campaign-funding Soroban workspace crate with campaign lifecycle operations, expiry-aware refunds, comprehensive unit tests, and transaction ledger snapshots.

Changes

Campaign funding contract

Layer / File(s) Summary
Package and contract model
contracts/Cargo.toml, contracts/campaign-funding/Cargo.toml, contracts/campaign-funding/src/lib.rs
Registers the crate and defines campaign state, errors, events, and contract helpers.
Campaign setup and contributions
contracts/campaign-funding/src/lib.rs
Implements initialization, campaign creation, contribution validation, token transfers, accounting, persistence, TTL updates, and queries.
Settlement and expiry
contracts/campaign-funding/src/lib.rs
Implements claims and refunds; qualifying refunds mark active campaigns as Expired and emit the expiry event.
Unit validation
contracts/campaign-funding/src/test.rs
Tests lifecycle success and failure paths, expiry behavior, queries, events, and initialization requirements.
Ledger snapshot validation
contracts/campaign-funding/test_snapshots/...
Adds expected authorization, ledger, storage, balance, and event snapshots for campaign scenarios.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Contributor
  participant CampaignFundingContract
  participant TokenContract
  Contributor->>CampaignFundingContract: refund(campaign_id, contributor)
  CampaignFundingContract->>TokenContract: transfer refund
  CampaignFundingContract->>CampaignFundingContract: mark contribution refunded
  CampaignFundingContract->>CampaignFundingContract: set status Expired and emit event
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding auto-refund behavior for expired campaign-funding contracts.
Linked Issues check ✅ Passed The changes align with the issue by adding the campaign-funding contract, expired-campaign refund handling, auth/error logic, and extensive tests.
Out of Scope Changes check ✅ Passed The added contract files, test code, and snapshots are all directly related to the linked campaign-funding implementation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
contracts/campaign-funding/src/test.rs (2)

606-625: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Loosen-to-strengthen: test_events_emitted only checks event count, not content.

assert!(events.len() >= 2) doesn't verify which events were emitted (e.g., campaign-created vs contribution-made) or their payloads, so it wouldn't catch a regression that emits the wrong event type while preserving count.

♻️ Suggested tightening
         let events = env.events().all();
-        assert!(events.len() >= 2);
+        assert!(events.len() >= 2, "expected at least a campaign-created and a contribution event");
+        // Optionally assert on event topics/data to catch wrong-event-type regressions,
+        // e.g. matching on the contract address and topic symbol for each expected event.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/campaign-funding/src/test.rs` around lines 606 - 625, Strengthen
test_events_emitted by asserting the emitted events’ topics and payloads, not
only that at least two events exist. Verify the campaign-created and
contribution-made events are present in the expected order and contain the
relevant campaign ID, addresses, and contribution amount.

169-188: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Clarify deadline error direction or split error codes.

Error(Contract, #8) is used for both past-deadline failures (test_contribute_after_deadline) and before-deadline failures (test_claim_before_deadline, test_refund_before_deadline). If this is a single shared DeadlineError variant, production failures won’t distinguish “deadline already passed” from “deadline not reached”, so use distinct codes/variants or document the intentionally shared semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/campaign-funding/src/test.rs` around lines 169 - 188, The deadline
validation errors are ambiguous because error code `#8` is used for both expired
and not-yet-reached campaigns. Update the relevant contract error variants and
checks used by test_contribute_after_deadline, test_claim_before_deadline, and
test_refund_before_deadline to return distinct codes for each direction, or
explicitly document the shared DeadlineError semantics if that behavior is
intentional.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/campaign-funding/src/lib.rs`:
- Around line 92-323: Add concise Rust documentation comments (`///`)
immediately before every public entrypoint in `CampaignFundingContract`:
`initialize`, `create_campaign`, `contribute`, `claim`, `refund`,
`get_campaign`, `get_contribution`, and `get_admin`. Describe each method’s
purpose and key parameters/return value where applicable, without changing
behavior.
- Around line 24-30: Assign CampaignStatus::Expired when a campaign deadline has
passed without reaching its goal. Update the relevant refund/deadline handling
flow around refund() so it persists the status transition before allowing
withdrawals, while preserving the existing eligibility checks and Active/Success
behavior.
- Around line 86-88: The fixed LEDGER_THRESHOLD and LEDGER_BUMP values do not
guarantee that entries survive until the deadline set by create_campaign. Update
create_campaign and its Campaign/Contribution TTL initialization to derive the
extension from deadline minus the current ledger time, including the required
margin, or enforce and validate a maximum deadline within the configured TTL
window before creating entries.
- Around line 160-178: Add a distinct error variant representing an expired
campaign deadline, then update contribute so its current_time >=
campaign.deadline guard panics with that variant instead of
Error::DeadlineNotReached. Preserve DeadlineNotReached for the pre-deadline
checks in claim and refund.
- Around line 95-104: Update initialize and the state-using methods
create_campaign, contribute, claim, and refund to require persisted
initialization before proceeding: detect missing admin state and return
Error::NotInitialized instead of relying on fallback values. Enforce admin
authorization on the operations intended to be admin-protected by loading the
stored admin and rejecting non-admin callers with Error::Unauthorized, while
preserving existing authentication and state-update behavior after validation.

---

Nitpick comments:
In `@contracts/campaign-funding/src/test.rs`:
- Around line 606-625: Strengthen test_events_emitted by asserting the emitted
events’ topics and payloads, not only that at least two events exist. Verify the
campaign-created and contribution-made events are present in the expected order
and contain the relevant campaign ID, addresses, and contribution amount.
- Around line 169-188: The deadline validation errors are ambiguous because
error code `#8` is used for both expired and not-yet-reached campaigns. Update the
relevant contract error variants and checks used by
test_contribute_after_deadline, test_claim_before_deadline, and
test_refund_before_deadline to return distinct codes for each direction, or
explicitly document the shared DeadlineError semantics if that behavior is
intentional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d902fad-64e1-4014-b5d5-281dee026894

📥 Commits

Reviewing files that changed from the base of the PR and between 375c936 and 72d02a2.

📒 Files selected for processing (31)
  • contracts/Cargo.toml
  • contracts/campaign-funding/Cargo.toml
  • contracts/campaign-funding/src/lib.rs
  • contracts/campaign-funding/src/test.rs
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_already_withdrawn.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_before_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_goal_not_met.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_success.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_after_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_campaign_not_found.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_multiple_backers.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_multiple_from_same_backer.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_zero_amount.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_create_campaign.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_create_campaign_deadline_in_past.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_create_campaign_zero_goal.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_events_emitted.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_full_lifecycle_refund.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_full_lifecycle_success.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_get_campaign_not_found.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_get_contribution_none.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_initialize.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_re_initialize_fails.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_already_refunded.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_before_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_goal_met.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_multiple_backers.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_no_contribution.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_permissionless.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_success.1.json

Comment thread contracts/campaign-funding/src/lib.rs
Comment thread contracts/campaign-funding/src/lib.rs
Comment thread contracts/campaign-funding/src/lib.rs
Comment on lines +95 to +104
pub fn initialize(env: Env, admin: Address) {
if env.storage().instance().has(&Symbol::new(&env, "admin")) {
panic_with_error!(&env, Error::AlreadyInitialized);
}
admin.require_auth();

env.storage().instance().set(&Symbol::new(&env, "admin"), &admin);
env.storage().instance().set(&Symbol::new(&env, "campaign_counter"), &0u64);
env.storage().instance().extend_ttl(LEDGER_THRESHOLD, LEDGER_BUMP);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching lib.rs under campaigns-campaign-funding:"
fd -a 'lib.rs' . | sed 's#^\./##' | rg 'campaign-funding|campaign' || true

echo
echo "git ls-files relevant:"
git ls-files | rg 'campaign-funding|campaign' || true

echo
echo "Outline:"
file="$(fd 'lib.rs' contracts/campaign-funding/src | head -n1 || true)"
if [ -n "${file:-}" ]; then
  echo "file=$file"
  wc -l "$file"
  ast-grep outline "$file" || true
  echo
  echo "Lines 1-220:"
  sed -n '1,220p' "$file" | nl -ba
fi

echo
echo "Search errors/usages:"
rg -n "NotInitialized|Unauthorized|initialize|campaign_counter|admin|require_auth|unwrap_or" contracts/campaign-funding/src || true

Repository: Fundable-Protocol/stellar_client_os

Length of output: 2074


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "pwd:"
pwd
echo
echo "git status:"
git status --short || true
echo
echo "top-level files:"
git ls-files | head -80 || true
echo
echo "Find campaign-funding:"
fd -a 'lib.rs' . 2>/dev/null | sed 's#^\./##' | rg '(^|/)campaign-funding($|/)|campaign' || true
echo
echo "Search local tracked files for symbols:"
rg -n "Error::NotInitialized|Error::Unauthorized|fn initialize|campaign_counter|unwrap_or|admin.require_auth|admin" . 2>/dev/null | head -200 || true

Repository: Fundable-Protocol/stellar_client_os

Length of output: 2074


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'PWD: %s\n' "$(pwd)"
printf '\nTracked candidates:\n'
git ls-files | rg '(^|/)contracts/campaign-funding/src/lib\.rs$|campaign-funding|campaign' || true

printf '\nLocal file direct check:\n'
p='contracts/campaign-funding/src/lib.rs'
if [ -f "$p" ]; then
  echo "EXISTS $p"
  wc -l "$p"
  sed -n '1,190p' "$p" | nl -ba
else
  echo "MISSING $p"
fi

printf '\nLocal symbol search:\n'
if [ -d contracts/campaign-funding/src ]; then
  rg -n "Error::NotInitialized|Error::Unauthorized|fn initialize|campaign_counter|unwrap_or|admin.require_auth|admin|create_campaign|contribute|claim|refund" contracts/campaign-funding/src || true
else
  echo "contracts/campaign-funding/src directory not present"
fi

Repository: Fundable-Protocol/stellar_client_os

Length of output: 2074


🌐 Web query:

stellar_client_os contracts/campaign-funding/src/lib.rs initialize admin campaign_counter initialize

💡 Result:

The command you are referencing refers to initializing a Soroban smart contract, commonly found in crowdfunding DApp implementations on the Stellar network [1][2][3]. In the context of Soroban contracts (typically written in Rust), the initialize function is used to set the initial state of the contract, such as the admin address and various configuration parameters, upon deployment [2][3]. While there is no single universal codebase matching your exact string, the structure you described is common in Soroban crowdfunding projects [1][2][4]. Below is a breakdown of how these components are typically handled: 1. Initialization: The initialize function (often named init or initialize) is defined in the contract's main library file (e.g., lib.rs) and is invoked to persist critical data to the ledger [2][5][3]. 2. Storage Keys: Data like the admin account and campaign_counter are typically stored using an enum-based data key system to ensure type-safe access to contract storage [2]. 3. Campaign Counter: A campaign_counter is often used in factory-style contracts to generate deterministic IDs for new campaigns as they are deployed [5]. Example of typical initialization logic in a Soroban contract: #[contractimpl] impl CrowdfundingContract { pub fn initialize(env: Env, admin: Address, campaign_counter: u32) { // Logic to store the admin and counter in the contract's persistent storage env.storage.instance.set(&DataKey::Admin, &admin); env.storage.instance.set(&DataKey::CampaignCounter, &campaign_counter); } } To invoke this function via the Stellar CLI after deploying your contract, you would use a command similar to the following: stellar contract invoke \ --id <contract_id> \ --source <admin_key> \ --network \ -- initialize \ --admin <admin_address> \ --campaign_counter Please ensure you check the specific lib.rs file in your project or the relevant repository for the exact parameter names, as they can vary between different implementations [2][6].

Citations:


🌐 Web query:

"Fundable-Protocol/stellar_client_os" "campaign-funding" "lib.rs" "campaign_counter" "unwrap_or"

💡 Result:

The repository Fundable-Protocol/stellar_client_os exists within the Fundable-Protocol organization on GitHub [1][2], but there is no widely documented or standardized campaign_funding module, lib.rs file, or campaign_counter function associated with that specific repository in public documentation. The Fundable-Protocol primarily develops decentralized finance solutions on StarkNet (using Cairo) [3] and maintains various client repositories [1]. It is common in Rust development to use the unwrap_or pattern when handling Option<T> types, such as some_variable.unwrap_or_default() or some_variable.unwrap_or(value), to provide fallback values [4][5]. If you are encountering a compiler error or logic issue related to campaign_counter and unwrap_or in your local environment, ensure that: 1. The variable campaign_counter is of type Option<T> [4][6]. 2. The argument passed to unwrap_or matches the type T of the inner value [4]. 3. You are importing the necessary modules if campaign_counter is defined in a different file within your project structure. If this refers to a private project or a specific commit, please double-check the repository path and the file structure of your local clone.

Citations:


🌐 Web query:

site:github.com/Fundable-Protocol/stellar_client_os contracts/campaign-funding/src/lib.rs campaign_counter unwrap_or

💡 Result:

Based on the provided search results, there is no evidence of a file path specifically named contracts/campaign-funding/src/lib.rs containing a variable or function called campaign_counter used with the unwrap_or method. While the search results identify various crowdfunding-related projects and their directory structures—such as fundlr-contracts, substrate-recipes, and solfundme—none of these repositories match the specific file path or code pattern requested [1][2][3]. The method unwrap_or is a standard Rust idiom for handling Option types by providing a default value if the Option is None, but no specific implementation of this in relation to a campaign_counter in the specified file path was found [1][2][3].

Citations:


Enforce admin/initialize() before using persisted state

admin and campaign_counter are only read with fallback handling, so create_campaign(), contribute(), claim(), and refund() can succeed even without initialize() and admin is never consulted for authorization. Make initialization required before using shared state, and have the admin gate the operations it’s meant to protect so Error::NotInitialized / Error::Unauthorized are not dead code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/campaign-funding/src/lib.rs` around lines 95 - 104, Update
initialize and the state-using methods create_campaign, contribute, claim, and
refund to require persisted initialization before proceeding: detect missing
admin state and return Error::NotInitialized instead of relying on fallback
values. Enforce admin authorization on the operations intended to be
admin-protected by loading the stored admin and rejecting non-admin callers with
Error::Unauthorized, while preserving existing authentication and state-update
behavior after validation.

Comment on lines +160 to +178
pub fn contribute(env: Env, campaign_id: u64, contributor: Address, amount: i128) {
contributor.require_auth();

if amount <= 0 {
panic_with_error!(&env, Error::InvalidAmount);
}

let mut campaign: Campaign = env.storage().persistent()
.get(&campaign_id)
.unwrap_or_else(|| panic_with_error!(&env, Error::CampaignNotFound));

if campaign.status != CampaignStatus::Active {
panic_with_error!(&env, Error::CampaignNotActive);
}

let current_time = env.ledger().timestamp();
if current_time >= campaign.deadline {
panic_with_error!(&env, Error::DeadlineNotReached);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Misleading error code when blocking contributions after the deadline.

At Line 176-178, current_time >= campaign.deadline (deadline has already passed) panics with Error::DeadlineNotReached — the exact opposite of what happened. Contrast with claim() (Line 226-229) and refund() (Line 265-268), where the same error is correctly used for current_time < campaign.deadline. Callers switching on error codes will get a misleading reason for the rejected contribution.

🐛 Proposed fix: add a distinct error for "deadline already passed"
     GoalNotMet = 10,
     AlreadyWithdrawn = 11,
     AlreadyRefunded = 12,
     ArithmeticOverflow = 13,
     DeadlineInPast = 14,
+    DeadlinePassed = 15,
 }
         let current_time = env.ledger().timestamp();
         if current_time >= campaign.deadline {
-            panic_with_error!(&env, Error::DeadlineNotReached);
+            panic_with_error!(&env, Error::DeadlinePassed);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/campaign-funding/src/lib.rs` around lines 160 - 178, Add a distinct
error variant representing an expired campaign deadline, then update contribute
so its current_time >= campaign.deadline guard panics with that variant instead
of Error::DeadlineNotReached. Preserve DeadlineNotReached for the pre-deadline
checks in claim and refund.

1. CampaignStatus::Expired now assigned — set in refund() when deadline
   passes without meeting goal; consumer-facing status is accurate.

2. TTL bump scaled to campaign deadline — campaign_ttl() computes
   (threshold, bump) proportional to deadline - now, ensuring entries
   survive until the campaign ends, capped at MAX_TTL (6312000).

3. Doc comments added to all public entrypoints (initialize,
   create_campaign, contribute, claim, refund, get_campaign,
   get_contribution, get_admin).

4. Initialization enforced — require_initialized() guard at the top of
   every state-mutating function; Error::NotInitialized is no longer
   dead code.

5. Wrong error in contribute() fixed — using CampaignNotActive (Fundable-Protocol#7)
   instead of DeadlineNotReached (Fundable-Protocol#8) when deadline has passed.

Also adds 4 new tests: expired status after refund, contribution to
expired campaign forbidden, and init enforcement for create + contribute.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/campaign-funding/src/lib.rs`:
- Around line 180-182: In the campaign creation flow using Self::campaign_ttl,
also extend instance storage with the same ttl and bump via
env.storage().instance().extend_ttl(ttl, bump). Keep the existing persistent
campaign write and persistent TTL extension unchanged.
- Around line 124-133: Update create_campaign and any related deadline
validation to reject future deadlines beyond the maximum retainable TTL horizon
before writing or extending the campaign entry. Reuse campaign_ttl’s
MAX_TTL/ledger timing semantics to calculate the allowed expiry boundary, while
preserving rejection of past deadlines and ensuring accepted campaigns remain
claimable or refundable until their deadline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 755c5676-df73-4d89-bd2a-39dc0c77f139

📥 Commits

Reviewing files that changed from the base of the PR and between 72d02a2 and 0b1ca7c.

📒 Files selected for processing (27)
  • contracts/campaign-funding/src/lib.rs
  • contracts/campaign-funding/src/test.rs
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_already_withdrawn.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_before_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_goal_not_met.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_success.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_after_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_multiple_backers.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_multiple_from_same_backer.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_to_expired_campaign_fails.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_without_init_fails.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_zero_amount.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_create_campaign.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_create_campaign_without_init_fails.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_events_emitted.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_full_lifecycle_refund.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_full_lifecycle_success.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_get_contribution_none.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_already_refunded.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_before_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_goal_met.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_multiple_backers.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_no_contribution.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_permissionless.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_sets_campaign_status_to_expired.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_success.1.json
🚧 Files skipped from review as they are similar to previous changes (19)
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_before_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_already_refunded.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_already_withdrawn.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_multiple_from_same_backer.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_goal_met.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_create_campaign.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_permissionless.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_full_lifecycle_success.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_success.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_full_lifecycle_refund.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_multiple_backers.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_events_emitted.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_claim_goal_not_met.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_after_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_before_deadline.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_refund_multiple_backers.1.json
  • contracts/campaign-funding/test_snapshots/test/test/test_contribute_zero_amount.1.json
  • contracts/campaign-funding/src/test.rs

Comment thread contracts/campaign-funding/src/lib.rs
Comment thread contracts/campaign-funding/src/lib.rs
…tainable deadlines

- Instance storage (admin, campaign_counter) now gets the same campaign-
  specific (ttl, bump) from campaign_ttl() at creation time, ensuring the
  contract stays available for the full campaign lifecycle.
- create_campaign rejects deadlines beyond MAX_DEADLINE_DELTA
  (~365 days) with Error::DeadlineTooFar (Fundable-Protocol#15), preventing campaigns that
  would be archived before their deadline.
- Adds test_create_campaign_deadline_too_far test.
@Skinny001

Copy link
Copy Markdown
Author

@Idrhas Done

@Skinny001

Copy link
Copy Markdown
Author

@Idrhas pls check and close the PR

@Idrhas

Idrhas commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates

1 similar comment
@Idrhas

Idrhas commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates

@coderabbitai coderabbitai Bot mentioned this pull request Aug 3, 2026
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Contract] Implement Auto-Refund Fallback Mechanism on Expired Campaigns

2 participants