-
Notifications
You must be signed in to change notification settings - Fork 370
The Ambassador Program #2002
base: master
Are you sure you want to change the base?
The Ambassador Program #2002
Changes from 8 commits
f885e71
8e76281
0541b4b
5cf1abf
07075dd
80df39c
8c288e3
5bd566d
5356d11
8112e48
0ffbaaf
b81ba28
9b89c1d
bbe116c
6104edb
f0c3a60
514fce9
1323be3
f200aef
1b867de
174a9a8
9017a8b
d602443
07c7d86
fe57ff2
5f111a2
2ef13d0
48363a7
a9926fb
5313c68
cb79c45
f867832
35e0417
d515363
c238fb2
9ab6aa4
67902de
54bd222
d5118b0
15ad2f5
9a562c5
08153e7
b7d8da7
273e9af
9ae1f68
fd88108
56baa6a
8efb683
f9c1c06
40bf76e
69036ba
421d3cf
1d625c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| [package] | ||
| name = "pallet-collective-content" | ||
| version = "0.1.0" | ||
| authors = ["Parity Technologies <[email protected]>"] | ||
| edition = "2021" | ||
| description = "Managed content" | ||
|
|
||
| [dependencies] | ||
| codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } | ||
| scale-info = { version = "2.3.0", default-features = false, features = ["derive"] } | ||
|
|
||
| frame-benchmarking = { git = "https://github.com/paritytech/substrate", optional = true, default-features = false, branch = "master" } | ||
| frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } | ||
| frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } | ||
|
|
||
| sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } | ||
| sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } | ||
| sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } | ||
|
|
||
| [dev-dependencies] | ||
| sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } | ||
|
|
||
| [features] | ||
| default = [ "std" ] | ||
| runtime-benchmarks = [ | ||
| "frame-benchmarking/runtime-benchmarks", | ||
| "frame-support/runtime-benchmarks", | ||
| "frame-system/runtime-benchmarks", | ||
| "sp-runtime/runtime-benchmarks", | ||
| ] | ||
|
|
||
| std = [ | ||
| "codec/std", | ||
| "scale-info/std", | ||
| "frame-support/std", | ||
| "frame-system/std", | ||
| "sp-core/std", | ||
| "sp-runtime/std", | ||
| "sp-std/std", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // Copyright (C) 2023 Parity Technologies (UK) Ltd. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| //! The pallet benchmarks. | ||
|
|
||
| use super::{Pallet as CollectiveContent, *}; | ||
| use frame_benchmarking::benchmarks_instance_pallet; | ||
| use frame_support::traits::{EnsureOrigin, UnfilteredDispatchable}; | ||
| use sp_core::Get; | ||
| use sp_std::vec; | ||
|
|
||
| fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) { | ||
| frame_system::Pallet::<T>::assert_last_event(generic_event.into()); | ||
| } | ||
|
|
||
| benchmarks_instance_pallet! { | ||
| set_charter { | ||
| let cid: Cid = b"ipfs_hash".to_vec().try_into().unwrap(); | ||
| let call = Call::<T, I>::set_charter { cid: cid.clone() }; | ||
| let origin = T::CharterOrigin::successful_origin(); | ||
| }: { call.dispatch_bypass_filter(origin)? } | ||
| verify { | ||
| assert_eq!(CollectiveContent::<T, I>::charter(), Some(cid.clone())); | ||
| assert_last_event::<T, I>(Event::NewCharterSet { cid }.into()); | ||
| } | ||
|
|
||
| announce { | ||
| let cid: Cid = b"ipfs_hash".to_vec().try_into().unwrap(); | ||
|
muharem marked this conversation as resolved.
Outdated
|
||
| let call = Call::<T, I>::announce { cid: cid.clone() }; | ||
| let origin = T::AnnouncementOrigin::successful_origin(); | ||
| }: { call.dispatch_bypass_filter(origin)? } | ||
| verify { | ||
| assert_eq!(CollectiveContent::<T, I>::announcements().len(), 1); | ||
| assert_last_event::<T, I>(Event::AnnouncementAnnounced { cid }.into()); | ||
| } | ||
|
|
||
| remove_announcement { | ||
| let cid: Cid = b"ipfs_hash".to_vec().try_into().unwrap(); | ||
| let origin = T::AnnouncementOrigin::successful_origin(); | ||
| let max_count = T::MaxAnnouncementsCount::get() as usize; | ||
|
|
||
| // fill the announcements vec for the worst case. | ||
| let announcements = vec![cid.clone(); max_count]; | ||
| let announcements: BoundedVec<_, T::MaxAnnouncementsCount> = BoundedVec::try_from(announcements).unwrap(); | ||
| Announcements::<T, I>::put(announcements); | ||
| assert_eq!(CollectiveContent::<T, I>::announcements().len(), max_count); | ||
|
|
||
| let call = Call::<T, I>::remove_announcement { cid: cid.clone() }; | ||
| }: { call.dispatch_bypass_filter(origin)? } | ||
| verify { | ||
| assert_eq!(CollectiveContent::<T, I>::announcements().len(), max_count - 1); | ||
| assert_last_event::<T, I>(Event::AnnouncementRemoved { cid }.into()); | ||
| } | ||
|
|
||
| impl_benchmark_test_suite!(CollectiveContent, super::mock::new_bench_ext(), super::mock::Test); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| // Copyright (C) 2023 Parity Technologies (UK) Ltd. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| //! Managed collective content. | ||
| //! | ||
| //! The pallet provides the functionality to store different types of the content. | ||
| //! The content presented as a [Cid] of the IPFS document which might contain any type of data. | ||
| //! Every type of the content has its own origin to be manage. The origins are configurable by clients. | ||
| //! Storing the content does not require a deposit, the content expected to be managed by a trusted collective. | ||
| //! | ||
| //! Content types: | ||
| //! - the collective [charter](pallet::Charter). A single document managed by [CharterOrigin](pallet::Config::CharterOrigin). | ||
| //! - the collective [announcements](pallet::Announcements). A list of announcements managed by [AnnouncementOrigin](pallet::Config::AnnouncementOrigin). | ||
|
|
||
| #![cfg_attr(not(feature = "std"), no_std)] | ||
|
|
||
| #[cfg(test)] | ||
| mod mock; | ||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| #[cfg(feature = "runtime-benchmarks")] | ||
| mod benchmarking; | ||
| pub mod weights; | ||
|
|
||
| pub use pallet::*; | ||
| pub use weights::WeightInfo; | ||
|
|
||
| use frame_support::BoundedVec; | ||
| use sp_core::ConstU32; | ||
|
|
||
| /// IPFS compatible CID. | ||
| // worst case 2 bytes base and codec, 2 bytes hash type and size, 64 bytes hash digest. | ||
|
muharem marked this conversation as resolved.
Outdated
|
||
| pub type Cid = BoundedVec<u8, ConstU32<68>>; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ive seen few implementations of the CID in or repos (at least two).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing CID as a bounded vec sounds good to me
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have a type for cid, maybe we should push it down to a more common place:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The alternative is we could call this type an OpaqueCid maybe?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. renaming to OpaqueCid |
||
|
|
||
| #[frame_support::pallet] | ||
| pub mod pallet { | ||
| use super::{Cid, WeightInfo}; | ||
| use frame_support::pallet_prelude::*; | ||
| use frame_system::pallet_prelude::*; | ||
|
|
||
| /// The current storage version. | ||
| const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); | ||
|
|
||
| #[pallet::pallet] | ||
| #[pallet::generate_store(pub (super) trait Store)] | ||
| #[pallet::storage_version(STORAGE_VERSION)] | ||
| pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if this setup works well for the Ambassador program, we might translate the Alliance into the similar setup, this is why the pallet generic over instance |
||
|
|
||
| /// The module configuration trait. | ||
| #[pallet::config] | ||
| pub trait Config<I: 'static = ()>: frame_system::Config { | ||
| /// The overarching event type. | ||
| type RuntimeEvent: From<Event<Self, I>> | ||
| + IsType<<Self as frame_system::Config>::RuntimeEvent>; | ||
|
|
||
| /// The origin to control the collective announcements. | ||
| type AnnouncementOrigin: EnsureOrigin<Self::RuntimeOrigin>; | ||
|
|
||
| /// The origin to control the collective charter. | ||
| type CharterOrigin: EnsureOrigin<Self::RuntimeOrigin>; | ||
|
|
||
| /// The maximum number of announcements. | ||
| #[pallet::constant] | ||
| type MaxAnnouncementsCount: Get<u32>; | ||
|
joepetrowski marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// Weights information needed for the pallet. | ||
| type WeightInfo: WeightInfo; | ||
| } | ||
|
|
||
| /// Errors encountered by the pallet (not a full list). | ||
| #[pallet::error] | ||
| pub enum Error<T, I = ()> { | ||
| /// The announcement is not found. | ||
| MissingAnnouncement, | ||
| /// Number of announcements exceeds `MaxAnnouncementsCount`. | ||
| TooManyAnnouncements, | ||
| } | ||
|
|
||
| /// Events emitted by the pallet. | ||
| #[pallet::event] | ||
| #[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
| pub enum Event<T: Config<I>, I: 'static = ()> { | ||
| /// A new charter has been set. | ||
| NewCharterSet { cid: Cid }, | ||
|
muharem marked this conversation as resolved.
Outdated
|
||
| /// A new announcement has been proposed. | ||
|
muharem marked this conversation as resolved.
Outdated
|
||
| AnnouncementAnnounced { cid: Cid }, | ||
| /// An on-chain announcement has been removed. | ||
| AnnouncementRemoved { cid: Cid }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curiosity killed the cat, but everyone is going to want to know why an announcement has been removed. Would be cool to have an enum of Outdated, Retracted, Other as to the announcement removal reason. |
||
| } | ||
|
|
||
| /// The collective charter. | ||
| #[pallet::storage] | ||
| #[pallet::getter(fn charter)] | ||
| pub type Charter<T: Config<I>, I: 'static = ()> = StorageValue<_, Cid, OptionQuery>; | ||
|
|
||
| /// The collective announcements. | ||
| #[pallet::storage] | ||
| #[pallet::getter(fn announcements)] | ||
| pub type Announcements<T: Config<I>, I: 'static = ()> = | ||
| StorageValue<_, BoundedVec<Cid, T::MaxAnnouncementsCount>, ValueQuery>; | ||
|
|
||
| #[pallet::call] | ||
| impl<T: Config<I>, I: 'static> Pallet<T, I> { | ||
| /// Set the collective charter. | ||
| /// | ||
| /// Parameters: | ||
| /// - `origin`: Must be the [T::CharterOrigin]. | ||
| /// - `cid`: [CID](super::Cid) of the IPFS document of the collective charter. | ||
| /// | ||
| /// Weight: `O(1)`. | ||
| #[pallet::call_index(0)] | ||
| #[pallet::weight(T::WeightInfo::set_charter())] | ||
| pub fn set_charter(origin: OriginFor<T>, cid: Cid) -> DispatchResult { | ||
| T::CharterOrigin::ensure_origin(origin)?; | ||
|
|
||
| Charter::<T, I>::put(&cid); | ||
|
|
||
| Self::deposit_event(Event::<T, I>::NewCharterSet { cid }); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Publish an announcement. | ||
| /// | ||
| /// Parameters: | ||
| /// - `origin`: Must be the [T::CharterOrigin]. | ||
| /// - `cid`: [CID](super::Cid) of the IPFS document to announce. | ||
| /// | ||
| /// Weight: `O(1)`. | ||
| #[pallet::call_index(1)] | ||
| #[pallet::weight(T::WeightInfo::announce())] | ||
| pub fn announce(origin: OriginFor<T>, cid: Cid) -> DispatchResult { | ||
|
muharem marked this conversation as resolved.
Outdated
|
||
| T::AnnouncementOrigin::ensure_origin(origin)?; | ||
|
|
||
| let mut announcements = <Announcements<T, I>>::get(); | ||
| announcements | ||
| .try_push(cid.clone()) | ||
| .map_err(|_| Error::<T, I>::TooManyAnnouncements)?; | ||
| <Announcements<T, I>>::put(announcements); | ||
|
|
||
| Self::deposit_event(Event::<T, I>::AnnouncementAnnounced { cid }); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Remove an announcement. | ||
| /// | ||
| /// Parameters: | ||
| /// - `origin`: Must be the [T::CharterOrigin]. | ||
| /// - `cid`: [CID](super::Cid) of the IPFS document to remove. | ||
| /// | ||
| /// Weight: `O(1)`, less of the [T::MaxAnnouncementsCount] is lower. | ||
| #[pallet::call_index(2)] | ||
| #[pallet::weight(T::WeightInfo::remove_announcement())] | ||
| pub fn remove_announcement(origin: OriginFor<T>, cid: Cid) -> DispatchResult { | ||
| T::AnnouncementOrigin::ensure_origin(origin)?; | ||
|
|
||
| let mut announcements = <Announcements<T, I>>::get(); | ||
| let pos = announcements | ||
| .binary_search(&cid) | ||
| .ok() | ||
| .ok_or(Error::<T, I>::MissingAnnouncement)?; | ||
| announcements.remove(pos); | ||
| <Announcements<T, I>>::put(announcements); | ||
|
|
||
| Self::deposit_event(Event::<T, I>::AnnouncementRemoved { cid }); | ||
| Ok(()) | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.