The Ambassador Program (#1308)

The Ambassador Program on Polkadot Collectives Parachain

The Polkadot Ambassador Program has existed for a while; more
information can be found
[here](https://wiki.polkadot.network/docs/ambassadors).
In this PR, the program is being brought on chain.

### On Chain Structure
The on-chain program consists of nine ranks, divided into four
categories ([full
list](https://github.com/paritytech/cumulus/blob/c238fb26b75569a11abb57437fd14acd26e05f18/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/mod.rs#L52)):
- Ambassadors (1-2 tiers)
- Senior Ambassadors (3-4 tiers)
- Head Ambassadors (5-7 tiers)
- Master Ambassadors (8-9 tiers)

Each rank has a corresponding `Origin` (e.g., `HeadAmbassadorsTier5` -
[full
list](https://github.com/paritytech/cumulus/blob/c238fb26b75569a11abb57437fd14acd26e05f18/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/origins.rs#L35)),
which represents the collective voice of members of that rank and above.

### Referendum

The `AmbassadorReferenda` instance of [referenda
pallet](https://docs.rs/pallet-referenda/latest/pallet_referenda/)
consists of [nine
tracks](https://github.com/paritytech/cumulus/blob/c238fb26b75569a11abb57437fd14acd26e05f18/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/tracks.rs#L51),
each corresponding to an `Origin`. A referendum taken on `senior
ambassador tier 4` track invites all members from rank 4 or above to
vote and commands `SeniorAmbassadors` `Origin`. Every member gets one
vote plus an additional vote for every excess rank. The referendum
proposal can be submitted by any member of a senior rank or above.

### Membership Management

Initial members will be brought on chain via migration, with subsequent
member management handled through the `AmbassadorCollective` instance of
[ranked collective
pallet](https://docs.rs/pallet-ranked-collective/latest/pallet_ranked_collective/).
Both `Root` and `FellowshipAdmin` `Origins`, commanded via public
Polkadot referendum, can promote or demote members to and from any rank.
Members themselves also have the power to promote or demote via its
referendum, with a senior member vote by the rank two above the new /
current rank - [full
configuration](https://github.com/paritytech/cumulus/blob/9ab6aa47063d7e8b67ddc10d9c136037f99c03a3/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/mod.rs#L67).

### Content Management

The program's on-chain content is managed via the collectives content
pallet, allowing for setting its charter and making announcements. The
voice of head ambassadors have the authority to set the charter, while
announcements can be made by any senior rank member or through a
referendum among all members.

### Additional Functionality

The `AmbassadorCore` instance of [core fellowship
pallet](https://docs.rs/pallet-core-fellowship/latest/pallet_core_fellowship/)
decorates the ranked collectives pallet with features like salary
determination, activity/passivity registration, and the handling of
promotion and demotion periods. While the usage of this pallet is
optional in the first version, future updates will make it the exclusive
method for induction/promotion.

Periodic salaries in USDt, payable on Asset Hub, are introduced through
the [salary
pallet](https://docs.rs/pallet-salary/latest/pallet_salary/). This
requires induction into the ambassador core pallet.

Please for more information on the pallets' functionality refer to their
documentations.

### Next Steps:
- Migrate to seed the program members
- Mint ambassador NFT badges on Asset Hub when promoting
- Treasury pallet instance for the Ambassador Program

---------

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
This commit is contained in:
Muharem
2023-09-20 21:55:56 +02:00
committed by GitHub
parent d6b3fc0dbb
commit 9e403629d5
29 changed files with 2945 additions and 233 deletions
@@ -0,0 +1,88 @@
// 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::{impl_benchmark_test_suite, v2::*};
use frame_support::traits::EnsureOrigin;
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());
}
/// returns CID hash of 68 bytes of given `i`.
fn create_cid(i: u8) -> OpaqueCid {
let cid: OpaqueCid = [i; 68].to_vec().try_into().unwrap();
cid
}
#[instance_benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn set_charter() -> Result<(), BenchmarkError> {
let cid: OpaqueCid = create_cid(1);
let origin =
T::CharterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
#[extrinsic_call]
_(origin as T::RuntimeOrigin, cid.clone());
assert_eq!(Charter::<T, I>::get(), Some(cid.clone()));
assert_last_event::<T, I>(Event::NewCharterSet { cid }.into());
Ok(())
}
#[benchmark]
fn announce() -> Result<(), BenchmarkError> {
let expire_at = DispatchTime::<_>::At(10u32.into());
let now = frame_system::Pallet::<T>::block_number();
let cid: OpaqueCid = create_cid(1);
let origin = T::AnnouncementOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
#[extrinsic_call]
_(origin as T::RuntimeOrigin, cid.clone(), Some(expire_at.clone()));
assert_eq!(<Announcements<T, I>>::count(), 1);
assert_last_event::<T, I>(
Event::AnnouncementAnnounced { cid, expire_at: expire_at.evaluate(now) }.into(),
);
Ok(())
}
#[benchmark]
fn remove_announcement() -> Result<(), BenchmarkError> {
let cid: OpaqueCid = create_cid(1);
let origin = T::AnnouncementOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
CollectiveContent::<T, I>::announce(origin.clone(), cid.clone(), None)
.expect("could not publish an announcement");
assert_eq!(<Announcements<T, I>>::count(), 1);
#[extrinsic_call]
_(origin as T::RuntimeOrigin, cid.clone());
assert_eq!(<Announcements<T, I>>::count(), 0);
assert_last_event::<T, I>(Event::AnnouncementRemoved { cid }.into());
Ok(())
}
impl_benchmark_test_suite!(CollectiveContent, super::mock::new_bench_ext(), super::mock::Test);
}
@@ -0,0 +1,206 @@
// 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 Pallet
//!
//! The pallet provides the functionality to store different types of content. This would typically
//! be used by an on-chain collective, such as the Polkadot Alliance or Ambassador Program.
//!
//! The pallet stores content as an [OpaqueCid], which should correspond to some off-chain hosting
//! service, such as IPFS, and contain any type of data. Each type of content has its own origin
//! from which it can be managed. The origins are configurable in the runtime. Storing content does
//! not require a deposit, as it is expected to be managed by a trusted collective.
//!
//! Content types:
//!
//! - Collective [charter](pallet::Charter): A single document (`OpaqueCid`) managed by
//! [CharterOrigin](pallet::Config::CharterOrigin).
//! - 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::{traits::schedule::DispatchTime, BoundedVec};
use sp_core::ConstU32;
use sp_std::prelude::*;
/// IPFS compatible CID.
// Worst case 2 bytes base and codec, 2 bytes hash type and size, 64 bytes hash digest.
pub type OpaqueCid = BoundedVec<u8, ConstU32<68>>;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{ensure, pallet_prelude::*};
use frame_system::pallet_prelude::*;
use sp_runtime::{traits::BadOrigin, Saturating};
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
/// 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>;
/// Default lifetime for an announcement before it expires.
type AnnouncementLifetime: Get<BlockNumberFor<Self>>;
/// The origin to control the collective announcements.
type AnnouncementOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Maximum number of announcements in the storage.
#[pallet::constant]
type MaxAnnouncements: Get<u32>;
/// The origin to control the collective charter.
type CharterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Weight information needed for the pallet.
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// The announcement is not found.
MissingAnnouncement,
/// Number of announcements exceeds `MaxAnnouncementsCount`.
TooManyAnnouncements,
/// Cannot expire in the past.
InvalidExpiration,
}
#[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: OpaqueCid },
/// A new announcement has been made.
AnnouncementAnnounced { cid: OpaqueCid, expire_at: BlockNumberFor<T> },
/// An on-chain announcement has been removed.
AnnouncementRemoved { cid: OpaqueCid },
}
/// The collective charter.
#[pallet::storage]
pub type Charter<T: Config<I>, I: 'static = ()> = StorageValue<_, OpaqueCid, OptionQuery>;
/// The collective announcements.
#[pallet::storage]
pub type Announcements<T: Config<I>, I: 'static = ()> =
CountedStorageMap<_, Blake2_128Concat, OpaqueCid, BlockNumberFor<T>, OptionQuery>;
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Set the collective charter.
///
/// Parameters:
/// - `origin`: Must be the [Config::CharterOrigin].
/// - `cid`: [CID](super::OpaqueCid) of the IPFS document of the collective charter.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::set_charter())]
pub fn set_charter(origin: OriginFor<T>, cid: OpaqueCid) -> 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 [Config::AnnouncementOrigin].
/// - `cid`: [CID](super::OpaqueCid) of the IPFS document to announce.
/// - `maybe_expire`: Expiration block of the announcement. If `None`
/// [`Config::AnnouncementLifetime`]
/// used as a default.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::announce())]
pub fn announce(
origin: OriginFor<T>,
cid: OpaqueCid,
maybe_expire: Option<DispatchTime<BlockNumberFor<T>>>,
) -> DispatchResult {
T::AnnouncementOrigin::ensure_origin(origin)?;
let now = frame_system::Pallet::<T>::block_number();
let expire_at = maybe_expire
.map_or(now.saturating_add(T::AnnouncementLifetime::get()), |e| e.evaluate(now));
ensure!(expire_at > now, Error::<T, I>::InvalidExpiration);
ensure!(
T::MaxAnnouncements::get() > <Announcements<T, I>>::count(),
Error::<T, I>::TooManyAnnouncements
);
<Announcements<T, I>>::insert(cid.clone(), expire_at);
Self::deposit_event(Event::<T, I>::AnnouncementAnnounced { cid, expire_at });
Ok(())
}
/// Remove an announcement.
///
/// Transaction fee refunded for expired announcements.
///
/// Parameters:
/// - `origin`: Must be the [Config::AnnouncementOrigin] or signed for expired
/// announcements.
/// - `cid`: [CID](super::OpaqueCid) of the IPFS document to remove.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::remove_announcement())]
pub fn remove_announcement(
origin: OriginFor<T>,
cid: OpaqueCid,
) -> DispatchResultWithPostInfo {
let maybe_who = match T::AnnouncementOrigin::try_origin(origin) {
Ok(_) => None,
Err(origin) => Some(ensure_signed(origin)?),
};
let expire_at = <Announcements<T, I>>::get(cid.clone())
.ok_or(Error::<T, I>::MissingAnnouncement)?;
let now = frame_system::Pallet::<T>::block_number();
ensure!(maybe_who.is_none() || now >= expire_at, BadOrigin);
<Announcements<T, I>>::remove(cid.clone());
Self::deposit_event(Event::<T, I>::AnnouncementRemoved { cid });
if now >= expire_at {
return Ok(Pays::No.into())
}
Ok(Pays::Yes.into())
}
}
}
@@ -0,0 +1,107 @@
// 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.
//! Test utilities.
pub use crate as pallet_collective_content;
use crate::WeightInfo;
use frame_support::{
ord_parameter_types, parameter_types,
traits::{ConstU32, ConstU64},
weights::Weight,
};
use frame_system::EnsureSignedBy;
use sp_runtime::{traits::IdentityLookup, BuildStorage};
frame_support::construct_runtime!(
pub enum Test {
System: frame_system,
CollectiveContent: pallet_collective_content,
}
);
type AccountId = u64;
type Block = frame_system::mocking::MockBlock<Test>;
ord_parameter_types! {
pub const CharterManager: u64 = 1;
pub const AnnouncementManager: u64 = 2;
pub const SomeAccount: u64 = 3;
}
parameter_types! {
pub const AnnouncementLifetime: u64 = 100;
pub const MaxAnnouncements: u32 = 5;
}
impl pallet_collective_content::Config for Test {
type RuntimeEvent = RuntimeEvent;
type AnnouncementLifetime = AnnouncementLifetime;
type AnnouncementOrigin = EnsureSignedBy<AnnouncementManager, AccountId>;
type MaxAnnouncements = MaxAnnouncements;
type CharterOrigin = EnsureSignedBy<CharterManager, AccountId>;
type WeightInfo = CCWeightInfo;
}
impl frame_system::Config for Test {
type BaseCallFilter = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Nonce = u64;
type Block = Block;
type Hash = sp_core::H256;
type Hashing = sp_runtime::traits::BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}
pub struct CCWeightInfo;
impl WeightInfo for CCWeightInfo {
fn set_charter() -> Weight {
Weight::zero()
}
fn announce() -> Weight {
Weight::zero()
}
fn remove_announcement() -> Weight {
Weight::zero()
}
}
// Build test environment.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = RuntimeGenesisConfig::default().build_storage().unwrap().into();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
#[cfg(feature = "runtime-benchmarks")]
pub fn new_bench_ext() -> sp_io::TestExternalities {
RuntimeGenesisConfig::default().build_storage().unwrap().into()
}
@@ -0,0 +1,204 @@
// 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.
//! Tests.
use super::{mock::*, *};
use frame_support::{assert_noop, assert_ok, error::BadOrigin, pallet_prelude::Pays};
/// returns CID hash of 68 bytes of given `i`.
fn create_cid(i: u8) -> OpaqueCid {
let cid: OpaqueCid = [i; 68].to_vec().try_into().unwrap();
cid
}
#[test]
fn set_charter_works() {
new_test_ext().execute_with(|| {
// wrong origin.
let origin = RuntimeOrigin::signed(SomeAccount::get());
let cid = create_cid(1);
assert_noop!(CollectiveContent::set_charter(origin, cid), BadOrigin);
// success.
let origin = RuntimeOrigin::signed(CharterManager::get());
let cid = create_cid(2);
assert_ok!(CollectiveContent::set_charter(origin, cid.clone()));
assert_eq!(Charter::<Test, _>::get(), Some(cid.clone()));
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::NewCharterSet { cid }));
// reset. success.
let origin = RuntimeOrigin::signed(CharterManager::get());
let cid = create_cid(3);
assert_ok!(CollectiveContent::set_charter(origin, cid.clone()));
assert_eq!(Charter::<Test, _>::get(), Some(cid.clone()));
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::NewCharterSet { cid }));
});
}
#[test]
fn announce_works() {
new_test_ext().execute_with(|| {
let now = frame_system::Pallet::<Test>::block_number();
// wrong origin.
let origin = RuntimeOrigin::signed(SomeAccount::get());
let cid = create_cid(1);
assert_noop!(CollectiveContent::announce(origin, cid, None), BadOrigin);
// success.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(2);
let maybe_expire_at = None;
assert_ok!(CollectiveContent::announce(origin, cid.clone(), maybe_expire_at));
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::AnnouncementAnnounced {
cid,
expire_at: now.saturating_add(AnnouncementLifetime::get()),
}));
// one more. success.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(3);
let maybe_expire_at = None;
assert_ok!(CollectiveContent::announce(origin, cid.clone(), maybe_expire_at));
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::AnnouncementAnnounced {
cid,
expire_at: now.saturating_add(AnnouncementLifetime::get()),
}));
// one more with expire. success.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(4);
let maybe_expire_at = DispatchTime::<_>::After(10);
assert_ok!(CollectiveContent::announce(origin, cid.clone(), Some(maybe_expire_at)));
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::AnnouncementAnnounced {
cid,
expire_at: maybe_expire_at.evaluate(now),
}));
// one more with later expire. success.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(5);
let maybe_expire_at = DispatchTime::<_>::At(now + 20);
assert_ok!(CollectiveContent::announce(origin, cid.clone(), Some(maybe_expire_at)));
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::AnnouncementAnnounced {
cid,
expire_at: maybe_expire_at.evaluate(now),
}));
// one more with earlier expire. success.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(6);
let maybe_expire_at = DispatchTime::<_>::At(now + 5);
assert_ok!(CollectiveContent::announce(origin, cid.clone(), Some(maybe_expire_at)));
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::AnnouncementAnnounced {
cid,
expire_at: maybe_expire_at.evaluate(now),
}));
// one more with earlier expire. success.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(7);
let maybe_expire_at = DispatchTime::<_>::At(now + 5);
assert_eq!(<Announcements<Test, _>>::count(), MaxAnnouncements::get());
assert_noop!(
CollectiveContent::announce(origin, cid.clone(), Some(maybe_expire_at)),
Error::<Test>::TooManyAnnouncements
);
});
}
#[test]
fn remove_announcement_works() {
new_test_ext().execute_with(|| {
// wrong origin.
let origin = RuntimeOrigin::signed(CharterManager::get());
let cid = create_cid(8);
assert_noop!(
CollectiveContent::remove_announcement(origin, cid),
Error::<Test>::MissingAnnouncement
);
// missing announcement.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(9);
assert_noop!(
CollectiveContent::remove_announcement(origin, cid),
Error::<Test>::MissingAnnouncement
);
// wrong origin. announcement not yet expired.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(10);
assert_ok!(CollectiveContent::announce(origin.clone(), cid.clone(), None));
assert!(<Announcements<Test>>::contains_key(cid.clone()));
let origin = RuntimeOrigin::signed(SomeAccount::get());
assert_noop!(CollectiveContent::remove_announcement(origin, cid.clone()), BadOrigin);
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
assert_ok!(CollectiveContent::remove_announcement(origin, cid));
// success.
// remove first announcement and assert.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(11);
assert_ok!(CollectiveContent::announce(origin.clone(), cid.clone(), None));
assert!(<Announcements<Test>>::contains_key(cid.clone()));
let info = CollectiveContent::remove_announcement(origin.clone(), cid.clone()).unwrap();
assert_eq!(info.pays_fee, Pays::Yes);
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::AnnouncementRemoved {
cid: cid.clone(),
}));
assert_noop!(
CollectiveContent::remove_announcement(origin, cid.clone()),
Error::<Test>::MissingAnnouncement
);
assert!(!<Announcements<Test>>::contains_key(cid));
// remove expired announcement and assert.
let origin = RuntimeOrigin::signed(AnnouncementManager::get());
let cid = create_cid(12);
assert_ok!(CollectiveContent::announce(
origin.clone(),
cid.clone(),
Some(DispatchTime::<_>::At(10))
));
assert!(<Announcements<Test>>::contains_key(cid.clone()));
System::set_block_number(11);
let origin = RuntimeOrigin::signed(SomeAccount::get());
let info = CollectiveContent::remove_announcement(origin.clone(), cid.clone()).unwrap();
assert_eq!(info.pays_fee, Pays::No);
System::assert_last_event(RuntimeEvent::CollectiveContent(Event::AnnouncementRemoved {
cid: cid.clone(),
}));
assert_noop!(
CollectiveContent::remove_announcement(origin, cid),
Error::<Test>::MissingAnnouncement
);
});
}
@@ -0,0 +1,41 @@
// 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 weight info trait and its unit implementation.
use frame_support::weights::Weight;
/// Weights information needed for the pallet.
pub trait WeightInfo {
/// Returns the weight of the set_charter extrinsic.
fn set_charter() -> Weight;
/// Returns the weight of the announce extrinsic.
fn announce() -> Weight;
/// Returns the weight of the remove_announcement extrinsic.
fn remove_announcement() -> Weight;
}
/// Unit implementation of the [WeightInfo].
impl WeightInfo for () {
fn set_charter() -> Weight {
Weight::zero()
}
fn announce() -> Weight {
Weight::zero()
}
fn remove_announcement() -> Weight {
Weight::zero()
}
}