Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions contracts/boundless/src/datatypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,11 @@ pub struct CampaignStatusUpdated {
pub status: Status,
pub admin: Address,
}

#[contractevent]
pub struct FundsReleased {
pub campaign_id: u64,
pub milestone_id: u64,
pub amount: i128,
pub releaser: Address,
}
79 changes: 73 additions & 6 deletions contracts/boundless/src/logic/campaign.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::datatypes::{
Backer, BoundlessError, Campaign, CampaignCancelled, CampaignFunded, CampaignStatusUpdated,
Milestone, Status, DataKey
DataKey, FundsReleased, Milestone, MilestoneStatus, Status,
};
use crate::interface::{CampaignManagement, ContractManagement};
use crate::{BoundlessContract, BoundlessContractArgs, BoundlessContractClient};
Expand Down Expand Up @@ -64,11 +64,78 @@ impl CampaignManagement for BoundlessContract {
}

fn release_funds(env: Env, campaign_id: u64, milestone_id: u64) -> Result<(), BoundlessError> {
// TODO: campaign funds release logic
// - Get campaign and milestone
// - Validate milestone can be released
// - Update milestone status
// - Emit release event
// Get the campaign from storage
let campaign_key = crate::datatypes::DataKey::Campaign(campaign_id);
let mut campaign: Campaign = env
.storage()
.persistent()
.get(&campaign_key)
.ok_or(BoundlessError::CampaignNotFound)?;

// Validate campaign status allows fund release
match campaign.status {
Status::Failed => return Err(BoundlessError::InvalidOperation),
Status::Completed => return Err(BoundlessError::InvalidOperation),
_ => {} // Allow release for Pending and Active campaigns
}

// Find the milestone in the campaign's milestones list
let mut milestone_found = false;
let mut milestone_amount = 0i128;

for milestone in campaign.milestones.iter() {
if milestone.id == milestone_id {
milestone_found = true;
milestone_amount = milestone.amount;

// Validate milestone status allows release
match milestone.status {
MilestoneStatus::Approved => {
// Milestone can be released
}
MilestoneStatus::Released => {
return Err(BoundlessError::InvalidOperation); // Already released
}
MilestoneStatus::Rejected => {
return Err(BoundlessError::InvalidOperation); // Cannot release rejected milestone
}
MilestoneStatus::Pending => {
return Err(BoundlessError::InvalidOperation); // Must be approved first
}
}
break;
}
}

if !milestone_found {
return Err(BoundlessError::MilestoneNotFound);
}

// Update the milestone status to Released
let mut updated_milestones = Vec::new(&env);
for milestone in campaign.milestones.iter() {
if milestone.id == milestone_id {
let mut updated_milestone = milestone.clone();
updated_milestone.status = MilestoneStatus::Released;
updated_milestones.push_back(updated_milestone);
} else {
updated_milestones.push_back(milestone.clone());
}
}
campaign.milestones = updated_milestones;

// Store the updated campaign
env.storage().persistent().set(&campaign_key, &campaign);

// Emit the release event
FundsReleased {
campaign_id,
milestone_id,
amount: milestone_amount,
releaser: env.current_contract_address(),
}
.publish(&env);

Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/boundless/src/tests/admin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![cfg(test)]

use crate::{datatypes::BoundlessError, BoundlessContract, BoundlessContractClient};
use crate::{BoundlessContract, BoundlessContractClient};
use soroban_sdk::{
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, BytesN, Env,
Expand Down
Loading
Loading