diff --git a/README.md b/README.md index c0ca209..9c6a358 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ git clone https://github.com/openguild-labs/frame-challenges.git Go to **Participant Registration** section and register to be the workshop participants. Add the below to the list, replace any placeholder with your personal information. -``` -| 🦄 | Name | Github username | Your current occupation | -``` +| Emoji | Name | GitHub Username | Current Occupation | +|:-----:|------------------------|----------------|----------------------| +| 🔥 | Kurniadi Ahmad Wijaya | [ShinyQ](https://github.com/ShinyQ) | Software Engineer | - Step 5: `Commit` your code and push to the forked Github repository diff --git a/src/governance.rs b/src/governance.rs index f71f10b..7d1ad96 100644 --- a/src/governance.rs +++ b/src/governance.rs @@ -4,11 +4,12 @@ use std::collections::HashMap; pub trait GovernanceConfig: StakingConfig {} -pub struct Proposal { +pub struct Proposal { description: String, yes_votes: u32, no_votes: u32, status: ProposalStatus, + creator: T::AccountId, } #[derive(Clone)] @@ -19,14 +20,18 @@ pub enum ProposalStatus { } pub struct GovernancePallet { - pub proposals: HashMap, + pub proposals: HashMap>, pub votes: HashMap<(T::AccountId, u32), bool>, // (voter, proposal_id) -> vote_type next_proposal_id: u32, } impl GovernancePallet { pub fn new() -> Self { - todo!() + Self { + proposals: HashMap::new(), + votes: HashMap::new(), + next_proposal_id: 0, + } } // Create a new proposal @@ -35,7 +40,22 @@ impl GovernancePallet { creator: T::AccountId, description: String, ) -> Result { - todo!() + let proposal_id = self.next_proposal_id; + + self.proposals.insert( + proposal_id, + Proposal { + description, + yes_votes: 0, + no_votes: 0, + status: ProposalStatus::Active, + creator + }, + ); + + self.next_proposal_id += 1; + + Ok(proposal_id) } // Vote on a proposal (true = yes, false = no) @@ -45,17 +65,50 @@ impl GovernancePallet { proposal_id: u32, vote_type: bool, ) -> Result<(), &'static str> { - todo!() + let proposal = self.proposals.get_mut(&proposal_id) + .ok_or("Proposal does not exist")?; + + if !matches!(proposal.status, ProposalStatus::Active) { + return Err("Cannot vote on finalized proposal"); + } + + let vote_key = (voter.clone(), proposal_id); + + if self.votes.insert(vote_key, vote_type).is_some() { + return Err("Voter has already voted on this proposal"); + } + + if vote_type { + proposal.yes_votes += 1; + } else { + proposal.no_votes += 1; + } + + Ok(()) } // Get proposal details - pub fn get_proposal(&self, proposal_id: u32) -> Option<&Proposal> { - todo!() + pub fn get_proposal(&self, proposal_id: u32) -> Option<&Proposal> { + self.proposals.get(&proposal_id) } // Finalize a proposal (changes status based on votes) pub fn finalize_proposal(&mut self, proposal_id: u32) -> Result { - todo!() + let proposal = self.proposals + .get_mut(&proposal_id) + .ok_or("Proposal does not exist")?; + + if !matches!(proposal.status, ProposalStatus::Active) { + return Err("Cannot vote on finalized proposal"); + } + + proposal.status = if proposal.yes_votes > proposal.no_votes { + ProposalStatus::Approved + } else { + ProposalStatus::Rejected + }; + + Ok(proposal.status.clone()) } } @@ -86,6 +139,8 @@ mod tests { let proposal = governance.get_proposal(proposal_id).unwrap(); assert_eq!(proposal.yes_votes, 2); assert_eq!(proposal.no_votes, 1); + assert_eq!(proposal.description, "Increase validator rewards".to_string()); + assert_eq!(proposal.creator, alice); // Finalize proposal let status = governance.finalize_proposal(proposal_id).unwrap(); diff --git a/src/staking.rs b/src/staking.rs index 7bd646e..b34fa9e 100644 --- a/src/staking.rs +++ b/src/staking.rs @@ -16,32 +16,66 @@ pub struct StakingPallet { impl StakingPallet { pub fn new() -> Self { - todo!() + Self { + free_balances: HashMap::new(), + staked_balances: HashMap::new(), + } } // Set free balance for an account pub fn set_balance(&mut self, who: T::AccountId, amount: T::Balance) { - todo!() + self.free_balances.insert(who, amount); } // Stake tokens (move from free to staked) pub fn stake(&mut self, who: T::AccountId, amount: T::Balance) -> Result<(), &'static str> { - todo!() + let free_balance = self.free_balances.get(&who).copied() + .unwrap_or(T::Balance::zero()); + + let staked_balance = self.staked_balances.get(&who).copied() + .unwrap_or(T::Balance::zero()); + + let new_free = free_balance.checked_sub(&amount) + .ok_or("Insufficient balance")?; + + let new_staked = staked_balance.checked_add(&amount) + .ok_or("Overflow")?; + + self.free_balances.insert(who.clone(), new_free); + self.staked_balances.insert(who, new_staked); + + Ok(()) } + // Unstake tokens (move from staked to free) pub fn unstake(&mut self, who: T::AccountId, amount: T::Balance) -> Result<(), &'static str> { - todo!() + let staked_balance = self.staked_balances.get(&who).copied() + .unwrap_or(T::Balance::zero()); + + let free_balance = self.free_balances.get(&who).copied() + .unwrap_or(T::Balance::zero()); + + let new_staked = staked_balance.checked_sub(&amount) + .ok_or("Insufficient staked balance")?; + + let new_free = free_balance.checked_add(&amount) + .ok_or("Overflow")?; + + self.staked_balances.insert(who.clone(), new_staked); + self.free_balances.insert(who, new_free); + + Ok(()) } // Get free balance for an account pub fn get_free_balance(&self, who: T::AccountId) -> T::Balance { - todo!() + self.free_balances.get(&who).copied().unwrap_or(T::Balance::zero()) } // Get staked balance for an account pub fn get_staked_balance(&self, who: T::AccountId) -> T::Balance { - todo!() + self.staked_balances.get(&who).copied().unwrap_or(T::Balance::zero()) } }