Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pallet: Referenda Tracks #2

Merged
merged 2 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions substrate/frame/referenda-tracks/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# Remark Storage Pallet
# Referenda Tracks Pallet

Allows storing arbitrary data off chain.
- [`Config`][Config]
- [`Call`][Call]

## Overview

Manage referenda voting tracks.

## Interface

### Dispatchable Functions

- `insert` - Insert a new referenda Track.
- `update` - Update the configuration of an existing referenda Track.
- `remove` - Remove an existing track

License: Apache-2.0
31 changes: 31 additions & 0 deletions substrate/frame/referenda-tracks/src/impl_tracks_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use super::*;

impl<T: Config<I>, I> pallet_referenda::TracksInfo<BalanceOf<T, I>, BlockNumberFor<T>>
for Pallet<T, I>
{
type Id = T::TrackId;
type RuntimeOrigin = <T::RuntimeOrigin as OriginTrait>::PalletsOrigin;
type TracksIter = TracksIter<T, I>;

fn tracks() -> Self::TracksIter {
Tracks::<T, I>::iter().map(|(id, info)| Cow::Owned(Track { id, info }))
}
fn track_for(origin: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
OriginToTrackId::<T, I>::get(origin).ok_or(())
}
fn tracks_ids() -> Vec<Self::Id> {
TracksIds::<T, I>::get().into_inner()
}
fn info(id: Self::Id) -> Option<Cow<'static, TrackInfoOf<T, I>>> {
Tracks::<T, I>::get(id).map(Cow::Owned)
}
}

impl<T: Config<I>, I: 'static> Get<Vec<TrackOf<T, I>>> for crate::Pallet<T, I> {
fn get() -> Vec<TrackOf<T, I>> {
// expensive but it doesn't seem to be used anywhere
<Pallet<T, I> as pallet_referenda::TracksInfo<BalanceOf<T, I>, BlockNumberFor<T>>>::tracks()
.map(|t| t.into_owned())
.collect()
}
}
198 changes: 148 additions & 50 deletions substrate/frame/referenda-tracks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! Edit and manage referenda voring tracks.
//! # Referenda Tracks Pallet
//!
//! - [`Config`][Config]
//! - [`Call`][Call]
//!
//! ## Overview
//!
//! Manage referenda voting tracks.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
//! - [`insert`][`crate::Pallet::insert`] - Insert a new referenda Track.
//! - [`update`][`crate::Pallet::update`] - Update the configuration of an existing referenda Track.
//! - [`remove`][`crate::Pallet::remove`] - Remove an existing track

#![cfg_attr(not(feature = "std"), no_std)]

mod benchmarking;
mod impl_tracks_info;
pub mod weights;

#[cfg(test)]
Expand All @@ -29,99 +46,180 @@ mod tests;
use core::iter::Map;
use frame_support::{storage::PrefixIterator, traits::OriginTrait};
use frame_system::pallet_prelude::BlockNumberFor;
use pallet_referenda::{BalanceOf, PalletsOriginOf, Track, TrackIdOf, TrackInfoOf};
use pallet_referenda::{BalanceOf, PalletsOriginOf, Track, TrackInfoOf};
use sp_core::Get;
use sp_std::{borrow::Cow, vec::Vec};

pub use pallet::*;
pub use weights::WeightInfo;

type TrackIdOf<T, I> = <T as Config<I>>::TrackId;
type TrackOf<T, I> = Track<<T as Config<I>>::TrackId, BalanceOf<T, I>, BlockNumberFor<T>>;

type TracksIter<T, I> = Map<
PrefixIterator<(<T as Config<I>>::TrackId, TrackInfoOf<T, I>)>,
fn((<T as Config<I>>::TrackId, TrackInfoOf<T, I>)) -> Cow<'static, TrackOf<T, I>>,
>;

impl<T: Config<I>, I> pallet_referenda::TracksInfo<BalanceOf<T, I>, BlockNumberFor<T>>
for Pallet<T, I>
{
type Id = T::TrackId;
type RuntimeOrigin = <T::RuntimeOrigin as OriginTrait>::PalletsOrigin;
type TracksIter = TracksIter<T, I>;

fn tracks() -> Self::TracksIter {
Tracks::<T, I>::iter().map(|(id, info)| Cow::Owned(Track { id, info }))
}
fn track_for(origin: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
OriginToTrackId::<T, I>::get(origin).ok_or(())
}
fn tracks_ids() -> Vec<Self::Id> {
TracksIds::<T, I>::get().into_inner()
}
fn info(id: Self::Id) -> Option<Cow<'static, TrackInfoOf<T, I>>> {
Tracks::<T, I>::get(id).map(Cow::Owned)
}
}

impl<T: Config<I>, I: 'static> Get<Vec<TrackOf<T, I>>> for crate::Pallet<T, I> {
fn get() -> Vec<Track<T::TrackId, BalanceOf<T, I>, BlockNumberFor<T>>> {
// expensive but it doesn't seem to be used anywhere
<Pallet<T, I> as pallet_referenda::TracksInfo<BalanceOf<T, I>, BlockNumberFor<T>>>::tracks()
.map(|t| t.into_owned())
.collect()
}
}

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::{
pallet_prelude::*,
traits::{EnsureOrigin, EnsureOriginWithArg},
};
use frame_system::pallet_prelude::*;

#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config + pallet_referenda::Config<I> {
type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;

type UpdateOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, TrackIdOf<Self, I>>;

type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;

type TrackId: Parameter + Member + Copy + MaxEncodedLen + Ord;

type MaxTracks: Get<u32>;
// type WeightInfo: WeightInfo;
///
type WeightInfo: WeightInfo;
}

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

#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::call_index(0)]
#[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))]
pub fn udpate(origin: OriginFor<T>, _id: TrackIdOf<T, I>) -> DispatchResultWithPostInfo {
let _sender = ensure_signed(origin)?;
// Self::deposit_event(Event::Foo { sender });
Ok(().into())
}
}

#[pallet::storage]
pub type TracksIds<T: Config<I>, I: 'static = ()> =
StorageValue<_, BoundedVec<T::TrackId, <T as Config<I>>::MaxTracks>, ValueQuery>;
StorageValue<_, BoundedVec<TrackIdOf<T, I>, <T as Config<I>>::MaxTracks>, ValueQuery>;

#[pallet::storage]
pub type OriginToTrackId<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, PalletsOriginOf<T>, T::TrackId>;
StorageMap<_, Blake2_128Concat, PalletsOriginOf<T>, TrackIdOf<T, I>>;

#[pallet::storage]
pub type Tracks<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::TrackId, TrackInfoOf<T, I>>;
StorageMap<_, Blake2_128Concat, TrackIdOf<T, I>, TrackInfoOf<T, I>>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
// Foo(T::AccountId),
/// A new track has been inserted
Created { id: TrackIdOf<T, I> },
/// The information for a track has been updated
Updated { id: TrackIdOf<T, I> },
/// A track has been removed
Removed { id: TrackIdOf<T, I> },
}

#[pallet::error]
pub enum Error<T, I = ()> {}
pub enum Error<T, I = ()> {
/// The maxmimum amount of track IDs was exceeded
MaxTracksExceeded,
/// The specified ID for this track was not found
TrackIdNotFound,
/// The specified ID for this track was already existing
TrackIdAlreadyExisting,
/// The track cannot be removed
CannotRemove,
}

#[pallet::call(weight(<T as Config<I>>::WeightInfo))]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Insert a new referenda Track.
///
/// Parameters:
/// - `id`: The Id of the track to be inserted.
/// - `info`: The configuration of the track.
/// - `pallet_origin`: A generic origin that will be matched to the track.
///
/// Emits `Created` event when successful.
///
/// Weight: `O(1)`
#[pallet::call_index(0)]
pub fn insert(
origin: OriginFor<T>,
id: TrackIdOf<T, I>,
info: TrackInfoOf<T, I>,
pallet_origin: PalletsOriginOf<T>,
) -> DispatchResultWithPostInfo {
T::AdminOrigin::ensure_origin(origin)?;

ensure!(Tracks::<T, I>::get(id) == None, Error::<T, I>::TrackIdAlreadyExisting);

TracksIds::<T, I>::try_append(id).map_err(|_| Error::<T, I>::MaxTracksExceeded)?;
Tracks::<T, I>::set(id, Some(info));
OriginToTrackId::<T, I>::set(pallet_origin.clone(), Some(id));

Self::deposit_event(Event::Created { id });
Ok(().into())
}

/// Update the configuration of an existing referenda Track.
///
/// Parameters:
/// - `id`: The Id of the track to be updated.
/// - `info`: The new configuration of the track.
///
/// Emits `Updated` event when successful.
///
/// Weight: `O(1)`
#[pallet::call_index(1)]
pub fn update(
origin: OriginFor<T>,
id: TrackIdOf<T, I>,
info: TrackInfoOf<T, I>,
) -> DispatchResultWithPostInfo {
T::UpdateOrigin::ensure_origin(origin, &id)?;

Tracks::<T, I>::try_mutate(id, |track| {
if track.is_none() {
return Err(Error::<T, I>::TrackIdNotFound);
};

*track = Some(info);

Ok(())
})?;

Self::deposit_event(Event::Updated { id });
Ok(().into())
}

/// Remove an existing track
///
/// Parameters:
/// - `id`: The Id of the track to be deleted.
///
/// Emits `Removed` event when successful.
///
/// Weight: `O(n)`
#[pallet::call_index(2)]
pub fn remove(
origin: OriginFor<T>,
id: TrackIdOf<T, I>,
pallet_origin: PalletsOriginOf<T>,
) -> DispatchResultWithPostInfo {
T::AdminOrigin::ensure_origin(origin)?;

ensure!(Tracks::<T, I>::contains_key(id), Error::<T, I>::TrackIdNotFound);
ensure!(
OriginToTrackId::<T, I>::get(&pallet_origin) == Some(id),
DispatchError::BadOrigin
);

Tracks::<T, I>::remove(id);
OriginToTrackId::<T, I>::remove(pallet_origin);

TracksIds::<T, I>::try_mutate(|tracks_ids| {
let new_tracks_ids = tracks_ids.clone().into_iter().filter(|i| i != &id).collect();
*tracks_ids = BoundedVec::<_, _>::truncate_from(new_tracks_ids);

Ok::<(), DispatchError>(())
})?;

Self::deposit_event(Event::Removed { id });
Ok(().into())
}
}
}
47 changes: 43 additions & 4 deletions substrate/frame/referenda-tracks/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ use crate as pallet_referenda_tracks;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
ord_parameter_types, parameter_types,
traits::{ConstU16, ConstU32, ConstU64, EqualPrivilegeOnly, VoteTally},
traits::{ConstU16, ConstU32, ConstU64, EnsureOriginWithArg, EqualPrivilegeOnly, VoteTally},
weights::Weight,
};
use frame_system::{EnsureRoot, EnsureSignedBy};
use pallet_referenda::{PalletsOriginOf, TrackIdOf, TrackInfoOf, TracksInfo};
use scale_info::TypeInfo;
use sp_core::H256;
use sp_io::TestExternalities;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage, Perbill,
Expand Down Expand Up @@ -111,14 +113,32 @@ impl pallet_balances::Config for Test {
type MaxHolds = ();
}

pub struct EnsureOriginToTrack;
impl EnsureOriginWithArg<RuntimeOrigin, TrackIdOf<Test, ()>> for EnsureOriginToTrack {
type Success = ();

fn try_origin(
o: RuntimeOrigin,
id: &TrackIdOf<Test, ()>,
) -> Result<Self::Success, RuntimeOrigin> {
let track_id_for_origin: TrackIdOf<Test, ()> =
Tracks::track_for(&o.clone().caller).map_err(|_| o.clone())?;
frame_support::ensure!(&track_id_for_origin == id, o);

Ok(())
}
}

parameter_types! {
pub const MaxTracks: u32 = 2;
}
impl pallet_referenda_tracks::Config for Test {
type TrackId = u8;
type RuntimeEvent = RuntimeEvent;
type MaxTracks = MaxTracks;
// type WeightInfo = ();
type AdminOrigin = EnsureRoot<u64>;
type UpdateOrigin = EnsureOriginToTrack;
type WeightInfo = ();
}

parameter_types! {
Expand Down Expand Up @@ -177,13 +197,32 @@ impl<Class> VoteTally<u32, Class> for Tally {
}
}

pub fn new_test_ext() -> sp_io::TestExternalities {
pub fn new_test_ext(
maybe_tracks: Option<Vec<(TrackIdOf<Test, ()>, TrackInfoOf<Test, ()>, PalletsOriginOf<Test>)>>,
) -> sp_io::TestExternalities {
let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)];

let t = RuntimeGenesisConfig {
system: Default::default(),
balances: pallet_balances::GenesisConfig::<Test> { balances },
}
.build_storage()
.unwrap();
t.into()

let mut ext = TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);

if let Some(tracks) = maybe_tracks {
for (id, info, pallet_origin) in tracks {
crate::Pallet::<Test, ()>::insert(RuntimeOrigin::root(), id, info, pallet_origin)
.expect("can insert track");
}

System::reset_events();
} else {
}
});

ext
}
Loading
Loading