Skip to content
Merged
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
2 changes: 1 addition & 1 deletion contracts/contracts/group_treasury/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ impl GroupTreasuryContract {
if proposal.status != ProposalStatus::Active {
panic!("proposal is not pending");
}
if env.ledger().timestamp() >= proposal.expires_at {
if proposal.status == ProposalStatus::Expired || env.ledger().timestamp() >= proposal.expires_at {
panic!("proposal expired");
}
if env
Expand Down
1 change: 1 addition & 0 deletions contracts/contracts/group_treasury/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum ProposalStatus {
Passed,
Rejected,
Executed,
Expired,
}

#[contracttype]
Expand Down
24 changes: 23 additions & 1 deletion contracts/contracts/proposals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, String, Sy

pub use storage::{
DataKey, Proposal, ProposalCreatedEvent, ProposalExecutedEvent, ProposalFinalizedEvent,
ProposalStatus, VoteCastEvent,
ProposalStatus, VoteCastEvent, ProposalExpiredEvent,
};

// ── Contract ─────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -191,6 +191,28 @@ impl ProposalsContract {
new_status
}

pub fn finalize_expired_proposal(env: Env, proposal_id: u64) {
let mut proposal = Self::load_proposal(&env, proposal_id);

if !matches!(proposal.status, ProposalStatus::Active) {
panic!("proposal not Pending");
}
let now = env.ledger().timestamp();
if now <= proposal.expires_at {
panic!("proposal not expired");
}

proposal.status = ProposalStatus::Expired;
env.storage()
.instance()
.set(&DataKey::Proposal(proposal_id), &proposal);

env.events().publish(
(Symbol::new(&env, "proposal_expired"),),
ProposalExpiredEvent { id: proposal_id },
);
}

/// Execute a Passed proposal. Refuses unless `status == Passed`.
/// MVP execution simply flips the status to `Executed` and emits
/// the event; downstream wiring (treasury withdrawals, etc.) can
Expand Down
7 changes: 7 additions & 0 deletions contracts/contracts/proposals/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum ProposalStatus {
Passed,
Rejected,
Executed,
Expired,
}

#[contracttype]
Expand Down Expand Up @@ -71,6 +72,12 @@ pub struct ProposalFinalizedEvent {
pub no_votes: u32,
}

#[contracttype]
#[derive(Clone)]
pub struct ProposalExpiredEvent {
pub id: u64,
}

#[contracttype]
#[derive(Clone)]
pub struct ProposalExecutedEvent {
Expand Down
37 changes: 37 additions & 0 deletions contracts/contracts/proposals/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,43 @@ fn create_with_past_expiry_panics() {
);
}

#[test]
#[should_panic(expected = "proposal not expired")]
fn finalize_expired_before_expiry_panics() {
let env = Env::default();
let (client, _padmin, alice, _bob, _carol, _treasury, _tadmin, m, token_id) = setup(&env);

let id = create_proposal_in(&env, &client, &alice, 1_000, &m, &token_id, &alice, 1);
client.finalize_expired_proposal(&id);
}

#[test]
#[should_panic(expected = "proposal not Pending")]
fn finalize_expired_when_passed_panics() {
let env = Env::default();
let (client, _padmin, alice, _bob, _carol, _treasury, _tadmin, m, token_id) = setup(&env);

let id = create_proposal_in(&env, &client, &alice, 500, &m, &token_id, &alice, 1);

advance_time(&env, 501);
client.finalize_proposal(&id); // becomes Passed
client.finalize_expired_proposal(&id);
}

#[test]
fn finalize_expired_success() {
let env = Env::default();
let (client, _padmin, alice, _bob, _carol, _treasury, _tadmin, m, token_id) = setup(&env);

let id = create_proposal_in(&env, &client, &alice, 500, &m, &token_id, &alice, 1);

advance_time(&env, 501);
client.finalize_expired_proposal(&id);

let proposal = client.get_proposal(&id);
assert_eq!(proposal.status, ProposalStatus::Expired);
}

// ─────────────────────────────────────────────────────────────────────────────
// execute_withdraw acceptance criteria

Expand Down
Loading