From 7d7c722ec98a1f55e3ff19bb799ff50c2471da85 Mon Sep 17 00:00:00 2001 From: mijinummi Date: Sun, 29 Mar 2026 14:47:39 +0100 Subject: [PATCH 1/3] Add Proposal Expiry to MultisigGovernance (#436) --- contracts/multisig_governance/src/lib.rs | 114 +++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/contracts/multisig_governance/src/lib.rs b/contracts/multisig_governance/src/lib.rs index 15ebb895..5f6abf20 100644 --- a/contracts/multisig_governance/src/lib.rs +++ b/contracts/multisig_governance/src/lib.rs @@ -558,3 +558,117 @@ impl GovernanceContract { .expect("contract not initialized (4002)") } } + +#[derive(scale::Encode, scale::Decode, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub struct Proposal { + pub id: u64, + pub creator: AccountId, + pub approvals: Vec, + pub executed: bool, + pub expires_at: u64, // NEW: ledger height expiry +} + +#[ink(storage)] +pub struct MultisigGovernance { + proposals: Mapping, + next_proposal_id: u64, + expiry_window: u64, // NEW: configurable expiry window in ledgers + admin: AccountId, +} + +impl MultisigGovernance { + #[ink(constructor)] + pub fn new(admin: AccountId, expiry_window: u64) -> Self { + Self { + proposals: Mapping::default(), + next_proposal_id: 0, + expiry_window, + admin, + } + } + + #[ink(message)] + pub fn set_expiry_window(&mut self, new_window: u64) -> Result<(), String> { + if self.env().caller() != self.admin { + return Err(String::from("Only admin can set expiry window")); + } + self.expiry_window = new_window; + Ok(()) + } + + #[ink(message)] + pub fn create_proposal(&mut self) -> u64 { + let id = self.next_proposal_id; + self.next_proposal_id += 1; + + let current_ledger = Self::current_ledger(); + let expires_at = current_ledger + self.expiry_window; + + let proposal = Proposal { + id, + creator: self.env().caller(), + approvals: Vec::new(), + executed: false, + expires_at, + }; + + self.proposals.insert(id, &proposal); + id + } + + #[ink(message)] + pub fn approve_proposal(&mut self, proposal_id: u64) -> Result<(), String> { + let mut proposal = self.proposals.get(proposal_id).ok_or("Proposal not found")?; + let current_ledger = Self::current_ledger(); + + if current_ledger > proposal.expires_at { + return Err(String::from("Proposal expired")); + } + + let caller = self.env().caller(); + if !proposal.approvals.contains(&caller) { + proposal.approvals.push(caller); + } + + self.proposals.insert(proposal_id, &proposal); + Ok(()) + } + + #[ink(message)] + pub fn finalize_proposal(&mut self, proposal_id: u64) -> Result<(), String> { + let mut proposal = self.proposals.get(proposal_id).ok_or("Proposal not found")?; + let current_ledger = Self::current_ledger(); + + if current_ledger > proposal.expires_at { + return Err(String::from("Proposal expired")); + } + + if proposal.executed { + return Err(String::from("Already executed")); + } + + // Execute logic here... + proposal.executed = true; + self.proposals.insert(proposal_id, &proposal); + Ok(()) + } + + #[ink(message)] + pub fn cancel_expired_proposal(&mut self, proposal_id: u64) -> Result<(), String> { + let proposal = self.proposals.get(proposal_id).ok_or("Proposal not found")?; + let current_ledger = Self::current_ledger(); + + if current_ledger <= proposal.expires_at { + return Err(String::from("Proposal not expired yet")); + } + + self.proposals.remove(proposal_id); + Ok(()) + } + + fn current_ledger() -> u64 { + // Placeholder: integrate with environment ledger height + Self::env().block_number() + } +} From bafc05984d730652ccce42d60191e59cd37e4350 Mon Sep 17 00:00:00 2001 From: mijinummi Date: Mon, 30 Mar 2026 13:47:04 +0100 Subject: [PATCH 2/3] style: apply cargo fmt --- contracts/multisig_governance/src/lib.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/multisig_governance/src/lib.rs b/contracts/multisig_governance/src/lib.rs index 5f6abf20..6f141743 100644 --- a/contracts/multisig_governance/src/lib.rs +++ b/contracts/multisig_governance/src/lib.rs @@ -619,7 +619,10 @@ impl MultisigGovernance { #[ink(message)] pub fn approve_proposal(&mut self, proposal_id: u64) -> Result<(), String> { - let mut proposal = self.proposals.get(proposal_id).ok_or("Proposal not found")?; + let mut proposal = self + .proposals + .get(proposal_id) + .ok_or("Proposal not found")?; let current_ledger = Self::current_ledger(); if current_ledger > proposal.expires_at { @@ -637,7 +640,10 @@ impl MultisigGovernance { #[ink(message)] pub fn finalize_proposal(&mut self, proposal_id: u64) -> Result<(), String> { - let mut proposal = self.proposals.get(proposal_id).ok_or("Proposal not found")?; + let mut proposal = self + .proposals + .get(proposal_id) + .ok_or("Proposal not found")?; let current_ledger = Self::current_ledger(); if current_ledger > proposal.expires_at { @@ -656,7 +662,10 @@ impl MultisigGovernance { #[ink(message)] pub fn cancel_expired_proposal(&mut self, proposal_id: u64) -> Result<(), String> { - let proposal = self.proposals.get(proposal_id).ok_or("Proposal not found")?; + let proposal = self + .proposals + .get(proposal_id) + .ok_or("Proposal not found")?; let current_ledger = Self::current_ledger(); if current_ledger <= proposal.expires_at { From d920af0f776a69a790e52728a7ab6200790ee18c Mon Sep 17 00:00:00 2001 From: mijinummi Date: Tue, 31 Mar 2026 21:06:58 +0100 Subject: [PATCH 3/3] fix: resolve rebase conflicts, align event indexer and Jest config, remove Ink! code from Soroban contract --- contracts/multisig_governance/src/lib.rs | 123 ----------------------- 1 file changed, 123 deletions(-) diff --git a/contracts/multisig_governance/src/lib.rs b/contracts/multisig_governance/src/lib.rs index 6f141743..15ebb895 100644 --- a/contracts/multisig_governance/src/lib.rs +++ b/contracts/multisig_governance/src/lib.rs @@ -558,126 +558,3 @@ impl GovernanceContract { .expect("contract not initialized (4002)") } } - -#[derive(scale::Encode, scale::Decode, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub struct Proposal { - pub id: u64, - pub creator: AccountId, - pub approvals: Vec, - pub executed: bool, - pub expires_at: u64, // NEW: ledger height expiry -} - -#[ink(storage)] -pub struct MultisigGovernance { - proposals: Mapping, - next_proposal_id: u64, - expiry_window: u64, // NEW: configurable expiry window in ledgers - admin: AccountId, -} - -impl MultisigGovernance { - #[ink(constructor)] - pub fn new(admin: AccountId, expiry_window: u64) -> Self { - Self { - proposals: Mapping::default(), - next_proposal_id: 0, - expiry_window, - admin, - } - } - - #[ink(message)] - pub fn set_expiry_window(&mut self, new_window: u64) -> Result<(), String> { - if self.env().caller() != self.admin { - return Err(String::from("Only admin can set expiry window")); - } - self.expiry_window = new_window; - Ok(()) - } - - #[ink(message)] - pub fn create_proposal(&mut self) -> u64 { - let id = self.next_proposal_id; - self.next_proposal_id += 1; - - let current_ledger = Self::current_ledger(); - let expires_at = current_ledger + self.expiry_window; - - let proposal = Proposal { - id, - creator: self.env().caller(), - approvals: Vec::new(), - executed: false, - expires_at, - }; - - self.proposals.insert(id, &proposal); - id - } - - #[ink(message)] - pub fn approve_proposal(&mut self, proposal_id: u64) -> Result<(), String> { - let mut proposal = self - .proposals - .get(proposal_id) - .ok_or("Proposal not found")?; - let current_ledger = Self::current_ledger(); - - if current_ledger > proposal.expires_at { - return Err(String::from("Proposal expired")); - } - - let caller = self.env().caller(); - if !proposal.approvals.contains(&caller) { - proposal.approvals.push(caller); - } - - self.proposals.insert(proposal_id, &proposal); - Ok(()) - } - - #[ink(message)] - pub fn finalize_proposal(&mut self, proposal_id: u64) -> Result<(), String> { - let mut proposal = self - .proposals - .get(proposal_id) - .ok_or("Proposal not found")?; - let current_ledger = Self::current_ledger(); - - if current_ledger > proposal.expires_at { - return Err(String::from("Proposal expired")); - } - - if proposal.executed { - return Err(String::from("Already executed")); - } - - // Execute logic here... - proposal.executed = true; - self.proposals.insert(proposal_id, &proposal); - Ok(()) - } - - #[ink(message)] - pub fn cancel_expired_proposal(&mut self, proposal_id: u64) -> Result<(), String> { - let proposal = self - .proposals - .get(proposal_id) - .ok_or("Proposal not found")?; - let current_ledger = Self::current_ledger(); - - if current_ledger <= proposal.expires_at { - return Err(String::from("Proposal not expired yet")); - } - - self.proposals.remove(proposal_id); - Ok(()) - } - - fn current_ledger() -> u64 { - // Placeholder: integrate with environment ledger height - Self::env().block_number() - } -}