diff --git a/src/governance.rs b/src/governance.rs index 4a8c727..8a27451 100644 --- a/src/governance.rs +++ b/src/governance.rs @@ -28,7 +28,7 @@ pub struct MultiSigConfig { impl Default for MultiSigConfig { fn default() -> Self { Self { - required_weight: 3, + required_weight: 1, max_signer_weight: 1, } } @@ -92,19 +92,18 @@ pub fn set_governance_config(env: &Env, config: &GovernanceConfig) { } pub fn verify_upgrade_quorum(env: &Env, signers: &Vec
) -> Result<(), ContractError> { - let config = get_governance_config(env); let data: ContractData = env .storage() .instance() .get(&DATA_KEY) .ok_or(ContractError::NotInitialized)?; + let authorized_signers: Map = env .storage() .instance() .get(&SIGNERS_KEY) .unwrap_or_else(|| Map::new(env)); - // Check both legacy count-based and new weight-based quorum let config = get_governance_config(env); let multisig_config = get_multisig_config(env); diff --git a/src/lib.rs b/src/lib.rs index c41f385..2db8d9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,8 +95,10 @@ pub mod upgrades; pub mod validation; use crate::governance::{ verify_staged_delay, StagedUpgrade, VotingBallot, open_ballot, cast_vote, close_ballot, - verify_upgrade_quorum, GovernanceUpgradeProposal, + verify_upgrade_quorum, GovernanceUpgradeProposal, GovernanceUpgradeProposedEvent, + calculate_collected_weight, get_multisig_config, GOVERNANCE_UPGRADE_KEY, }; +use crate::events::events::{emit_simple2, EV_UPGRADE_PROPOSED}; use crate::validation::{check_bond_capacity, validate_telemetry_submission}; use crate::governance::{ @@ -434,17 +436,51 @@ impl TimeLockedUpgradeContract { pub fn propose_upgrade( env: Env, new_wasm_hash: BytesN<32>, proposer: Address, + signers: Vec
, nonce: u64, salt: Bytes, salt_signature: BytesN<32>, sig_expires_at: u64, ) -> Result<(), ContractError> { - pub fn propose_upgrade(env: Env, new_wasm_hash: BytesN<32>, proposer: Address, nonce: u64, salt: Bytes, salt_signature: BytesN<32>, sig_expires_at: u64) -> Result<(), ContractError> { if env.ledger().timestamp() > sig_expires_at { return Err(ContractError::SignatureExpired); } crate::staging::check_staging_access(&env, &proposer)?; let data = Self::_load_data(&env)?; if data.admin != proposer { return Err(ContractError::NotAdmin); } proposer.require_auth(); consume_nonce(&env, &proposer, nonce, salt, salt_signature)?; - let staged = StagedUpgrade { new_wasm_hash, proposer, staged_at: env.ledger().timestamp() }; + + // Verify multi-sig quorum threshold + let collected_weight = calculate_collected_weight(&env, &signers, &data)?; + let multisig_config = get_multisig_config(&env); + if collected_weight < multisig_config.required_weight { + return Err(ContractError::ThresholdNotReached); + } + + let staged_at = env.ledger().timestamp(); + let proposal = GovernanceUpgradeProposal { + new_wasm_hash, + proposer: proposer.clone(), + staged_at, + signers: signers.clone(), + }; + env.storage().instance().set(&GOVERNANCE_UPGRADE_KEY, &proposal); + + let staged = StagedUpgrade { new_wasm_hash, proposer: proposer.clone(), staged_at }; env.storage().instance().set(&PENDING_UPGRADE_KEY, &staged); + + // Emit GovernanceUpgradeProposed event + let _ = emit_simple2( + &env, + EV_UPGRADE_PROPOSED, + symbol_short!("governance"), + GovernanceUpgradeProposedEvent { + new_wasm_hash, + proposer: proposer.clone(), + signers, + staged_at, + required_weight: multisig_config.required_weight, + collected_weight, + }, + ); + + crate::core::instance::bump_instance_ttl(&env); Ok(()) } diff --git a/src/test.rs b/src/test.rs index bb8ab52..fbe0192 100644 --- a/src/test.rs +++ b/src/test.rs @@ -250,7 +250,9 @@ fn test_execute_upgrade_post_health_check() { let new_wasm_hash = soroban_sdk::BytesN::from_array(&env, &[1u8; 32]); let (salt, signature) = nonce_proof(&env, 0, b"propose-upgrade-health"); - client.propose_upgrade(&new_wasm_hash, &admin, &0, &salt, &signature, &u64::MAX); + let signers = soroban_sdk::vec![&env, admin.clone()]; + client.register_signer(&admin, &admin); + client.propose_upgrade(&new_wasm_hash, &admin, &signers, &0, &salt, &signature, &u64::MAX); // Fast forward time by 48 hours advance_ledger_timestamp(&env, UPGRADE_DELAY_SECONDS); diff --git a/tests/unit.rs b/tests/unit.rs index 5d83727..86c43f3 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -326,7 +326,9 @@ fn test_upgrade_propose_and_get_pending() { let (env, client, admin) = setup_env(); let wasm_hash = BytesN::from_array(&env, &[0u8; 32]); let (salt, sig) = nonce_proof(&env, 0, b"upgrade-test"); - client.propose_upgrade(&wasm_hash, &admin, &0, &salt, &sig, &u64::MAX); + let signers = soroban_sdk::vec![&env, admin.clone()]; + client.register_signer(&admin, &admin); + client.propose_upgrade(&wasm_hash, &admin, &signers, &0, &salt, &sig, &u64::MAX); let pending = client.get_pending_upgrade(); assert!(pending.is_some()); } @@ -336,7 +338,9 @@ fn test_upgrade_cancel() { let (env, client, admin) = setup_env(); let wasm_hash = BytesN::from_array(&env, &[0u8; 32]); let (salt, sig) = nonce_proof(&env, 0, b"upgrade-test"); - client.propose_upgrade(&wasm_hash, &admin, &0, &salt, &sig, &u64::MAX); + let signers = soroban_sdk::vec![&env, admin.clone()]; + client.register_signer(&admin, &admin); + client.propose_upgrade(&wasm_hash, &admin, &signers, &0, &salt, &sig, &u64::MAX); client.cancel_upgrade(&admin); let pending = client.get_pending_upgrade(); assert!(pending.is_none()); @@ -347,7 +351,9 @@ fn test_upgrade_timelock_remaining() { let (env, client, admin) = setup_env(); let wasm_hash = BytesN::from_array(&env, &[0u8; 32]); let (salt, sig) = nonce_proof(&env, 0, b"upgrade-test"); - client.propose_upgrade(&wasm_hash, &admin, &0, &salt, &sig, &u64::MAX); + let signers = soroban_sdk::vec![&env, admin.clone()]; + client.register_signer(&admin, &admin); + client.propose_upgrade(&wasm_hash, &admin, &signers, &0, &salt, &sig, &u64::MAX); let remaining = client.get_upgrade_timelock_remaining(); assert!(remaining.is_some()); }