Build on Internet Computer with ICP Rust CDK
Creating Functions 3
Finalizing the Voting Smart Contract: end_proposal and vote Functions
In the previous video, we built the edit_proposal function. In this lesson, we finalize the smart contract by implementing the remaining two core update functions: end_proposal and vote. These complete the functionality of our decentralized voting system.
Function 1: end_proposal
This function deactivates a proposal, preventing further votes.
Logic Breakdown:
- Retrieve the Proposal
- Use the proposal key to fetch the entry from the state.
- If the proposal does not exist, return a VoteError::NoSuchProposal.
- Verify Ownership
- Ensure the caller is the proposal’s creator.
- If not, return a VoteError::AccessRejected.
- Update State
- Set is_active to false.
- Reinsert the modified proposal into the state.
- If insertion succeeds, return Ok(()); otherwise, return VoteError::UpdateError.
Sample Implementation Outline:
#[update]
fn end_proposal(key: u64) -> Result<(), VoteError> {
PROPOSAL_MAP.with(|p| {
let mut p = p.borrow_mut();
let proposal = match p.get(&key) {
Some(p) => p.clone(),
None => return Err(VoteError::NoSuchProposal),
};
if ic_cdk::caller() != proposal.owner {
return Err(VoteError::AccessRejected);
}
let mut updated = proposal;
updated.is_active = false;
let result = p.insert(key, updated);
match result {
Some(_) => Ok(()),
None => Err(VoteError::UpdateError),
}
})
}
Function 2: vote
This function allows users to cast a vote (approve, reject, or pass) on an active proposal.
Logic Breakdown:
- Retrieve the Proposal
- Similar to the previous function, check for the proposal’s existence.
- Return VoteError::NoSuchProposal if not found.
- Check Voting Eligibility
- Ensure the user has not already voted (by checking the voted list).
- Ensure the proposal is still active.
- If either check fails, return the appropriate VoteError.
- Cast the Vote
- Increment the appropriate vote count (approve, reject, or pass) based on the Choice enum.
- Add the caller’s address to the voted vector to prevent double voting.
- Update the State
- Insert the updated proposal.
- Return Ok(()) on success or VoteError::UpdateError on failure.
Sample Implementation Outline:
#[update]
fn vote(key: u64, choice: Choice) -> Result<(), VoteError> {
PROPOSAL_MAP.with(|p| {
let mut p = p.borrow_mut();
let mut proposal = match p.get(&key) {
Some(p) => p.clone(),
None => return Err(VoteError::NoSuchProposal),
};
let caller = ic_cdk::caller();
if proposal.voted.contains(&caller) {
return Err(VoteError::AlreadyVoted);
}
if !proposal.is_active {
return Err(VoteError::ProposalIsNotActive);
}
match choice {
Choice::Approve => proposal.approve += 1,
Choice::Reject => proposal.reject += 1,
Choice::Pass => proposal.pass += 1,
}
proposal.voted.push(caller);
let result = p.insert(key, proposal);
match result {
Some(_) => Ok(()),
None => Err(VoteError::UpdateError),
}
})
}
Conclusion
With the addition of end_proposal and vote, your voting smart contract is now complete. Each function follows a consistent pattern:
- Retrieve state data.
- Validate it.
- Apply logic.
- Write it back safely.
- Return a clear result or error.
This pattern ensures the contract is secure, maintainable, and easy to understand.
Comments
You need to enroll in the course to be able to comment!