use crate::helpers::event::{ProposalCancelled, ProposalCreated, ProposalExecuted}; use crate::permission::types::*; use crate::*; use near_sdk::{env, Promise}; #[near] impl Contract { /// Create a new proposal for policy execution /// If execution_threshold is 1 and proposer has required role, executes immediately pub fn propose_execution( &mut self, policy_id: String, function_args: serde_json::Value, ) -> Option { let caller = env::predecessor_account_id(); let current_time = env::block_timestamp() as u128; // Get policy let policy = self .internal_get_active_policy(&policy_id) .unwrap_or_else(|| env::panic_str(&format!("Policy {} not found", policy_id))); // Validate caller has required role require!( self.internal_has_role(&caller, &policy.required_role), format!( "Caller {} does not have required role '{}'", caller, policy.required_role ) ); // Generate unique proposal ID self.kernel_state.proposal_counter += 1; let proposal_id = self.kernel_state.proposal_counter; // Calculate deadline using policy's expiry time let deadline_nano = current_time + policy.proposal_expiry_time_nanosec.0; // Create proposal let mut proposal = Proposal { id: proposal_id, policy_id: policy_id.clone(), function_args: serde_json::to_string(&function_args) .expect("Failed to serialize function_args"), proposer: caller.clone(), creation_time: U128(current_time), deadline: U128(deadline_nano), execution_threshold: policy.required_vote_count, }; // Store proposal self.kernel_state .proposals .insert(proposal_id, proposal.clone()); // Initialize empty vote list let empty_votes: Vec = Vec::new(); self.kernel_state .proposal_votes .insert(proposal_id, empty_votes); // Emit proposal created event ProposalCreated { proposal_id, policy_id: &policy_id, proposer: &caller, } .emit(); // Always cast a vote from the proposer let execution_result = self.internal_add_vote_and_check_execution(&mut proposal, caller); execution_result } /// Vote on a proposal - auto-executes if threshold is reached pub fn vote_on_proposal(&mut self, proposal_id: u64) -> Option { let caller = env::predecessor_account_id(); let current_time = env::block_timestamp() as u128; // Get proposal - store as owned value to avoid borrow conflicts let mut proposal = self .kernel_state .proposals .get(&proposal_id) .cloned() .unwrap_or_else(|| env::panic_str(&format!("Proposal {} not found", proposal_id))); // Check if expired require!(current_time <= proposal.deadline.0, "Proposal has expired"); // Get policy ID before mutable operations let policy_id = proposal.policy_id.clone(); // Get policy to check role requirements let policy = self .internal_get_active_policy(&policy_id) .unwrap_or_else(|| env::panic_str(&format!("Policy {} not found", policy_id))); // Validate voter has required role require!( self.internal_has_role(&caller, &policy.required_role), format!( "Voter {} does not have required role '{}'", caller, policy.required_role ) ); // Use the common voting function - pass mutable reference to owned proposal let execution_result = self.internal_add_vote_and_check_execution(&mut proposal, caller); execution_result } /// Cancel a proposal (only by proposer or admin) pub fn cancel_proposal(&mut self, proposal_id: u64) { let caller = env::predecessor_account_id(); // Get proposal let proposal = self .kernel_state .proposals .get(&proposal_id) .unwrap_or_else(|| env::panic_str(&format!("Proposal {} not found", proposal_id))); // Only proposer or owner can cancel require!( proposal.proposer == caller, "Only proposer can cancel proposal" ); // Emit proposal cancelled event ProposalCancelled { proposal_id, policy_id: &proposal.policy_id, } .emit(); // Clean up proposal from storage immediately to save storage costs // Events provide the audit trail self.internal_cleanup_proposal(proposal_id); } // ==================== View Methods ==================== /// Get proposal details pub fn get_proposal(&self, proposal_id: u64) -> Option { self.kernel_state.proposals.get(&proposal_id).cloned() } /// Get votes for a proposal pub fn get_proposal_votes(&self, proposal_id: u64) -> Vec { self.kernel_state .proposal_votes .get(&proposal_id) .cloned() .unwrap_or_default() } /// Get the latest proposal ID (current proposal counter) pub fn get_latest_proposal_id(&self) -> u64 { self.kernel_state.proposal_counter } /// Get count of pending proposals vs total proposals ever created pub fn get_proposal_count(&self) -> (u64, u64) { let total_created = self.kernel_state.proposal_counter; // All proposals in storage are pending (executed/cancelled ones are deleted) let pending_count = self.kernel_state.proposals.len() as u64; (pending_count, total_created) } /// Get pending proposals with pagination pub fn get_active_proposals( &self, from_index: Option, limit: Option, ) -> Vec { let start_index = from_index.unwrap_or(0); let max_limit = limit.unwrap_or(50).min(100) as usize; // Iterate only over existing proposals (no sparse ID checking) self.kernel_state .proposals .iter() .skip(start_index as usize) .take(max_limit) .map(|(_, proposal)| proposal.clone()) .collect() } /// Get user's pending proposals pub fn get_user_active_proposals(&self, account_id: AccountId) -> Vec { // Iterate only over existing proposals and filter by proposer self.kernel_state .proposals .iter() .filter(|(_, proposal)| proposal.proposer == account_id) .map(|(_, proposal)| proposal.clone()) .collect() } // ==================== Pending Actions Functions ==================== /// Check if lock can be safely released (no pending actions) pub fn can_release_lock(&self) -> bool { self.kernel_state.pending_actions.is_empty() } /// Get count of pending actions pub fn get_pending_actions_count(&self) -> u64 { self.kernel_state.pending_actions.len() as u64 } } impl Contract { /// Core voting mechanics: add vote, update counts, and execute if threshold reached fn internal_add_vote_and_check_execution( &mut self, proposal: &mut Proposal, voter: AccountId, ) -> Option { let current_time = env::block_timestamp() as u128; // Get existing votes let mut votes = self .kernel_state .proposal_votes .get(&proposal.id) .cloned() .unwrap_or_default(); // Check for duplicate vote for existing_vote in &votes { require!( existing_vote.voter != voter, format!("Account {} has already voted on this proposal", voter) ); } // Add new vote let new_vote = Vote { proposal_id: proposal.id, voter: voter.clone(), timestamp: U128(current_time), }; votes.push(new_vote); // Save updated votes self.kernel_state .proposal_votes .insert(proposal.id, votes.clone()); // Check if threshold reached for auto-execution let should_execute = votes.len() as u32 >= proposal.execution_threshold; let mut execution_result: Option = None; if should_execute { // Execute the proposal execution_result = self.internal_execute_proposal_function(&proposal); // Check if this execution completes any pending actions self.internal_complete_matching_pending_actions(&proposal.policy_id); // Generate pending actions if policy requires them self.internal_generate_pending_actions(&proposal); // Emit proposal executed event ProposalExecuted { proposal_id: proposal.id, policy_id: &proposal.policy_id, } .emit(); // Clean up proposal from storage immediately to save storage costs self.internal_cleanup_proposal(proposal.id); } else { // Save updated proposal with new vote counts self.kernel_state .proposals .insert(proposal.id, proposal.clone()); } execution_result } /// Execute the actual proposal function - this replaces the old signature-based execution fn internal_execute_proposal_function(&mut self, proposal: &Proposal) -> Option { // Get policy to determine type let policy = self .internal_get_active_policy(&proposal.policy_id) .unwrap_or_else(|| { env::panic_str(&format!( "Policy {} not found during execution", proposal.policy_id )) }); // Parse function_args back to JSON Value let function_args: serde_json::Value = serde_json::from_str(&proposal.function_args) .expect("Failed to parse function_args JSON"); self.kernel_state.last_executed_proposal_timestamp = U128(env::block_timestamp() as u128); match policy.policy_type { PolicyType::KernelConfiguration => self .internal_execute_kernel_function_via_proposal(&proposal.policy_id, &function_args), PolicyType::ChainSigTransaction => { // Validate that proposer holds the global lock self.internal_validate_lock_held(&proposal.proposer); Some(self.internal_execute_sign_chain_sig( &function_args, policy.policy_details.as_chain_sig_transaction().unwrap(), )) } PolicyType::ChainSigMessage => { // Validate that proposer holds the global lock self.internal_validate_lock_held(&proposal.proposer); Some(self.internal_execute_sign_message( &function_args, policy.policy_details.as_chain_sig_message().unwrap(), )) } PolicyType::NearNativeTransaction => { // Validate that proposer holds the global lock self.internal_validate_lock_held(&proposal.proposer); Some(self.internal_execute_near_native_transaction( &function_args, policy.policy_details.as_near_native_transaction().unwrap(), )) } } } /// Generate pending actions based on the executed proposal and its policy fn internal_generate_pending_actions(&mut self, proposal: &Proposal) { // Get the policy to check for required pending actions let Some(policy) = self.internal_get_active_policy(&proposal.policy_id) else { return; // No policy found, skip generating actions }; // Skip if no pending actions are required if policy.required_pending_actions.is_empty() { return; } for policy_id in &policy.required_pending_actions { // Generate action with empty args - policy restrictions will handle validation self.internal_create_pending_action(policy_id, proposal.id); } } /// Create a new pending action pub fn internal_create_pending_action(&mut self, policy_id: &str, created_by_proposal: u64) { // insert() returns true if inserted, false if already exists let was_inserted = self .kernel_state .pending_actions .insert(policy_id.to_string()); if was_inserted { env::log_str(&format!( "Created pending action for policy {} from proposal {}", policy_id, created_by_proposal )); } } /// Clean up proposal and its votes from storage immediately /// This saves storage costs by removing completed proposals fn internal_cleanup_proposal(&mut self, proposal_id: u64) { // Remove proposal from storage self.kernel_state.proposals.remove(&proposal_id); // Remove all votes for this proposal self.kernel_state.proposal_votes.remove(&proposal_id); env::log_str(&format!("Cleaned up proposal {} from storage", proposal_id)); } // ==================== Pending Action Management ==================== /// Force complete a pending action without execution (emergency use) pub fn internal_force_complete_pending_action(&mut self, policy_id: String) { // Remove from storage using efficient Set removal require!( self.kernel_state.pending_actions.remove(&policy_id), format!("Pending action for policy {} not found", policy_id) ); env::log_str(&format!( "Force completed and removed pending action for policy {}", policy_id )); } /// Check if the executed policy completes a pending action pub fn internal_complete_matching_pending_actions(&mut self, executed_policy_id: &str) { if self .kernel_state .pending_actions .remove(&executed_policy_id.to_string()) { env::log_str(&format!( "Completed and removed pending action for policy {}", executed_policy_id )); } } }