Skip to content

Commit

Permalink
simple approval voting
Browse files Browse the repository at this point in the history
  • Loading branch information
amiyatulu committed Oct 17, 2024
1 parent 4103cfb commit 0393f64
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ members = [
"custom-pallets/support",
"custom-pallets/departments",
"runtime",
"custom-pallets/simple-approval-voting",
]
resolver = "2"
[profile.release]
Expand Down Expand Up @@ -70,6 +71,7 @@ pallet-project-tips = { path = "custom-pallets/project-tips", default-features =
pallet-positive-externality = { path = "custom-pallets/positive-externality", default-features = false }
pallet-department-funding = { path = "custom-pallets/department-funding", default-features = false }
pallet-departments = { path = "custom-pallets/departments", default-features = false }
pallet-simple-approval-voting = { path = "custom-pallets/simple-approval-voting", default-features = false }

## Traits
trait-sortition-sum-game = { path = "traits/trait-sortition-sum-game", default-features = false }
Expand Down
37 changes: 37 additions & 0 deletions custom-pallets/simple-approval-voting/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "pallet-simple-approval-voting"
version = "4.0.0-dev"
description = "FRAME pallet template for defining custom runtime logic."
authors = ["Substrate DevHub <https://github.com/substrate-developer-hub>"]
homepage = "https://substrate.io"
edition = "2021"
license = "MIT-0"
publish = false
repository = "https://github.com/substrate-developer-hub/substrate-node-template/"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }

[dev-dependencies]
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }

[features]
default = ["std"]
std = [
"parity-scale-codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
]
runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"]
try-runtime = ["frame-support/try-runtime"]
1 change: 1 addition & 0 deletions custom-pallets/simple-approval-voting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
License: MIT-0
35 changes: 35 additions & 0 deletions custom-pallets/simple-approval-voting/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Benchmarking setup for pallet-template
#![cfg(feature = "runtime-benchmarks")]
use super::*;

#[allow(unused)]
use crate::Pallet as Template;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;

#[benchmarks]
mod benchmarks {
use super::*;

#[benchmark]
fn do_something() {
let value = 100u32.into();
let caller: T::AccountId = whitelisted_caller();
#[extrinsic_call]
do_something(RawOrigin::Signed(caller), value);

assert_eq!(Something::<T>::get(), Some(value));
}

#[benchmark]
fn cause_error() {
Something::<T>::put(100u32);
let caller: T::AccountId = whitelisted_caller();
#[extrinsic_call]
cause_error(RawOrigin::Signed(caller));

assert_eq!(Something::<T>::get(), Some(101u32));
}

impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test);
}
184 changes: 184 additions & 0 deletions custom-pallets/simple-approval-voting/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#![cfg_attr(not(feature = "std"), no_std)]

/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::*;

#[frame_support::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

#[pallet::pallet]
pub struct Pallet<T>(_);

/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
}

// The pallet's runtime storage items.
// https://docs.substrate.io/main-docs/build/runtime-storage/
#[pallet::storage]
#[pallet::getter(fn something)]
// Learn more about declaring storage items:
// https://docs.substrate.io/main-docs/build/runtime-storage/#declaring-storage-items
pub type Something<T> = StorageValue<_, u32>;

// Storage to keep track of all candidates
#[pallet::storage]
#[pallet::getter(fn candidates)]
pub type Candidates<T: Config> = StorageValue<_, Vec<T::AccountId>, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn votes)]
pub type Votes<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, u32, ValueQuery>;

// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event documentation should end with an array that provides descriptive names for event
/// parameters. [something, who]
SomethingStored {
something: u32,
who: T::AccountId,
},
CandidateAdded {
candidate: T::AccountId,
},
VoteCast {
user: T::AccountId,
},
WinnerAnnounced {
winner: T::AccountId,
},
TopCandidates {
top_candidates: Vec<(T::AccountId, u32)>,
},
}

// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// Error names should be descriptive.
NoneValue,
/// Errors should have helpful documentation associated with them.
StorageOverflow,
CandidateExists,
NoSuchCandidate,
AlreadyVoted,
}

// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(0)]
pub fn add_candidate(origin: OriginFor<T>, candidate: T::AccountId) -> DispatchResult {
let who = ensure_signed(origin)?;

// Ensure candidate is not already added
ensure!(!Candidates::<T>::get().contains(&candidate), Error::<T>::CandidateExists);

// Add the candidate
Candidates::<T>::mutate(|candidates| candidates.push(candidate.clone()));

// Emit an event
Self::deposit_event(Event::CandidateAdded { candidate });

Ok(())
}

#[pallet::call_index(1)]
#[pallet::weight(0)]
pub fn vote(
origin: OriginFor<T>,
approved_candidates: Vec<T::AccountId>,
) -> DispatchResult {
let who = ensure_signed(origin)?;

// Ensure the candidates exist and increment votes
for candidate in approved_candidates.iter() {
ensure!(Candidates::<T>::get().contains(candidate), Error::<T>::NoSuchCandidate);
Votes::<T>::mutate(candidate, |vote_count| *vote_count += 1);
}

// Emit an event for the vote
Self::deposit_event(Event::VoteCast { user: who });

Ok(())
}

#[pallet::call_index(2)]
#[pallet::weight(0)]
pub fn announce_winner(origin: OriginFor<T>) -> DispatchResult {
let _who = ensure_signed(origin)?;

// Find the candidate with the highest votes
let mut winner: Option<T::AccountId> = None;
let mut max_votes = 0;

for candidate in Candidates::<T>::get().iter() {
let votes = Votes::<T>::get(candidate);
if votes > max_votes {
max_votes = votes;
winner = Some(candidate.clone());
}
}

if let Some(winner) = winner {
// Emit the winner announcement event
Self::deposit_event(Event::WinnerAnnounced { winner });
}

Ok(())
}

#[pallet::call_index(3)]
#[pallet::weight(0)]
pub fn find_top_candidates(origin: OriginFor<T>) -> DispatchResult {
let _who = ensure_signed(origin)?;

// Collect all candidates and their votes
let mut candidate_votes: Vec<(T::AccountId, u32)> = Candidates::<T>::get()
.into_iter()
.map(|candidate| {
let votes = Votes::<T>::get(&candidate);
(candidate, votes)
})
.collect();

// Sort candidates by votes in descending order
candidate_votes.sort_by(|a, b| b.1.cmp(&a.1));

// Take the top 5 candidates
let top_candidates = candidate_votes.into_iter().take(5).collect::<Vec<_>>();

// Emit an event with the top 5 candidates
Self::deposit_event(Event::TopCandidates { top_candidates: top_candidates.clone() });

Ok(())
}
}
}
61 changes: 61 additions & 0 deletions custom-pallets/simple-approval-voting/src/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate as pallet_template;
use frame_support::{
derive_impl,
traits::{ConstU16, ConstU64},
};
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};

type Block = frame_system::mocking::MockBlock<Test>;

// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
TemplateModule: pallet_template,
}
);

#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Nonce = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ConstU16<42>;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}

impl pallet_template::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}

// Build genesis storage according to the mock runtime.
pub fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap()
.into()
}
33 changes: 33 additions & 0 deletions custom-pallets/simple-approval-voting/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::{mock::*, Error, Event};
use frame_support::{assert_noop, assert_ok};

#[test]
fn it_works_for_default_value() {
new_test_ext().execute_with(|| {
// Go past genesis block so events get deposited
System::set_block_number(1);
// Dispatch a signed extrinsic.
assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42));
// Read pallet storage and assert an expected result.
assert_eq!(TemplateModule::something(), Some(42));
// Assert that the correct event was deposited
System::assert_last_event(
Event::SomethingStored {
something: 42,
who: 1,
}
.into(),
);
});
}

#[test]
fn correct_error_for_none_value() {
new_test_ext().execute_with(|| {
// Ensure the expected error is thrown when no value is present.
assert_noop!(
TemplateModule::cause_error(RuntimeOrigin::signed(1)),
Error::<Test>::NoneValue
);
});
}
Loading

0 comments on commit 0393f64

Please sign in to comment.