diff --git a/Cargo.lock b/Cargo.lock index eaac99e..614650d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1279,6 +1279,18 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "fc-pallet-pitch" +version = "0.1.0" +dependencies = [ + "pallet-balances", + "parity-scale-codec", + "polkadot-sdk-frame", + "scale-info", + "sp-io", + "sp-runtime", +] + [[package]] name = "fc-pallet-referenda-tracks" version = "1.0.0" diff --git a/pallets/pitch/Cargo.toml b/pallets/pitch/Cargo.toml new file mode 100644 index 0000000..58c01c9 --- /dev/null +++ b/pallets/pitch/Cargo.toml @@ -0,0 +1,42 @@ +[package] +authors.workspace = true +description = "Ephemeral, consent-gated economic cells keyed by H3 cell references" +edition.workspace = true +license.workspace = true +name = "fc-pallet-pitch" +repository.workspace = true +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec.workspace = true +frame.workspace = true +scale-info.workspace = true +sp-runtime.workspace = true + +[dev-dependencies] +pallet-balances.workspace = true +sp-io.workspace = true + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "frame/std", + "pallet-balances/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", +] +try-runtime = [ + "frame/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/pallets/pitch/README.md b/pallets/pitch/README.md new file mode 100644 index 0000000..491fb38 --- /dev/null +++ b/pallets/pitch/README.md @@ -0,0 +1,21 @@ +# Pitch Pallet + +`fc-pallet-pitch` records time-boxed economic authority over H3 cells. + +A pitch is a short-lived claim over the economic membrane of a place: it can be +publicly indexed by raw H3 cell, hidden behind a commitment, gated by a +membership proof, or sealed with only an opaque on-chain residue. The pallet +does not perform H3 geometry. Clients compute H3 cells off-chain and submit +either the raw `u64` cell or a commitment. + +This first implementation focuses on the minimal consensus surface: + +- claim, amend, join, dissolve, and reap a pitch; +- index public raw-cell pitches for cheap lookup; +- keep private pitches out of the raw-cell index; +- record selective disclosure grants without putting the disclosed secret + on-chain; +- derive a deterministic sovereign account for each pitch. + +Fee routing, VOS/Noir verification, and off-chain residue transport are intended +as runtime integrations layered on top of this base. diff --git a/pallets/pitch/src/lib.rs b/pallets/pitch/src/lib.rs new file mode 100644 index 0000000..68ef46a --- /dev/null +++ b/pallets/pitch/src/lib.rs @@ -0,0 +1,462 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +//! # Pitch Pallet +//! +//! Time-boxed economic authority over H3 cells, private by default. +//! +//! The pallet stores sparse pitch records keyed by raw H3 cell ids or opaque +//! commitments. H3 geometry remains off-chain; the runtime only stores and +//! indexes claims. + +use frame::prelude::*; +use sp_runtime::{traits::AccountIdConversion, Permill}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub mod weights; +pub use weights::*; + +pub use pallet::*; + +#[derive( + Clone, + Copy, + Debug, + Decode, + DecodeWithMemTracking, + Encode, + MaxEncodedLen, + PartialEq, + Eq, + TypeInfo, +)] +pub enum Disclosure { + Public, + Unlisted, + Gated, + Sealed, +} + +#[derive( + Clone, Debug, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen, PartialEq, Eq, TypeInfo, +)] +pub enum CellRef { + Raw(u64), + Commitment(Hash), +} + +#[derive( + Clone, + Copy, + Debug, + Decode, + DecodeWithMemTracking, + Encode, + MaxEncodedLen, + PartialEq, + Eq, + TypeInfo, +)] +pub enum SettlementPolicy { + Holder, + Licensor, + Members, + Community(CommunityId), +} + +#[derive( + Clone, + Copy, + Debug, + Decode, + DecodeWithMemTracking, + Encode, + MaxEncodedLen, + PartialEq, + Eq, + TypeInfo, +)] +pub enum DisclosureScope { + Location, + Membership, + Activity, + All, +} + +#[derive( + Clone, Debug, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen, PartialEq, Eq, TypeInfo, +)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound(T: Config))] +pub struct Pitch { + pub disclosure: Disclosure, + pub cell: CellRef, + pub resolution: Option, + pub start: BlockNumberFor, + pub end: BlockNumberFor, + pub holder: T::AccountId, + pub licensor: Option, + pub local_tax: Permill, + pub membership_root: Option, + pub on_dissolve: SettlementPolicy, + pub residue: Option>, +} + +pub type PitchId = u64; +pub type MembershipProofOf = BoundedVec::MaxProofLen>; +pub type ResidueOf = BoundedVec::MaxResidueLen>; + +pub trait MembershipVerifier { + fn verify(who: &AccountId, root: &Hash, proof: &[u8]) -> bool; +} + +impl MembershipVerifier for () { + fn verify(_: &AccountId, _: &Hash, _: &[u8]) -> bool { + false + } +} + +#[frame::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config>> { + type WeightInfo: WeightInfo; + + type CommunityId: Parameter + MaxEncodedLen + Copy; + + type Verifier: MembershipVerifier; + + #[pallet::constant] + type PalletId: Get; + + #[pallet::constant] + type MaxPitchesPerCell: Get; + + #[pallet::constant] + type MaxProofLen: Get; + + #[pallet::constant] + type MaxResidueLen: Get; + + #[pallet::constant] + type GracePeriod: Get>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type NextPitchId = StorageValue<_, PitchId, ValueQuery>; + + #[pallet::storage] + pub type Pitches = StorageMap<_, Blake2_128Concat, PitchId, Pitch>; + + #[pallet::storage] + pub type ByCell = + StorageMap<_, Twox64Concat, u64, BoundedVec, ValueQuery>; + + #[pallet::storage] + pub type Expiring = StorageMap< + _, + Twox64Concat, + BlockNumberFor, + BoundedVec, + ValueQuery, + >; + + #[pallet::storage] + pub type Joined = StorageDoubleMap< + _, + Blake2_128Concat, + PitchId, + Blake2_128Concat, + T::AccountId, + (), + OptionQuery, + >; + + #[pallet::storage] + pub type DisclosureGrants = StorageDoubleMap< + _, + Blake2_128Concat, + PitchId, + Blake2_128Concat, + T::AccountId, + DisclosureScope, + OptionQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Claimed { + pitch: PitchId, + disclosure: Disclosure, + holder: T::AccountId, + }, + Joined { + pitch: PitchId, + who: T::AccountId, + }, + Amended { + pitch: PitchId, + }, + Dissolved { + pitch: PitchId, + }, + DisclosureGranted { + pitch: PitchId, + to: T::AccountId, + scope: DisclosureScope, + }, + Reaped { + pitch: PitchId, + }, + } + + #[pallet::error] + pub enum Error { + BadCellRef, + BadResolution, + BadWindow, + ExpiryScheduleFull, + IndexFull, + NotHolder, + NotLive, + PitchMissing, + ProofInvalid, + WindowInPast, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::claim())] + pub fn claim( + origin: OriginFor, + cell: CellRef, + resolution: Option, + start: BlockNumberFor, + end: BlockNumberFor, + disclosure: Disclosure, + local_tax: Permill, + membership_root: Option, + on_dissolve: SettlementPolicy, + licensor: Option, + residue: Option>, + ) -> DispatchResult { + let holder = ensure_signed(origin)?; + Self::ensure_cell_matches_disclosure(&cell, disclosure)?; + Self::ensure_resolution(&cell, resolution)?; + Self::ensure_window(start, end)?; + + let pitch = NextPitchId::::get(); + let record = Pitch:: { + disclosure, + cell: cell.clone(), + resolution, + start, + end, + holder: holder.clone(), + licensor, + local_tax, + membership_root, + on_dissolve, + residue, + }; + + Pitches::::insert(pitch, record); + NextPitchId::::put(pitch.checked_add(1).ok_or(ArithmeticError::Overflow)?); + Self::index_pitch(pitch, &cell)?; + Expiring::::try_mutate(end.saturating_add(T::GracePeriod::get()), |ids| { + ids.try_push(pitch) + .map_err(|_| Error::::ExpiryScheduleFull) + })?; + + Self::deposit_event(Event::Claimed { + pitch, + disclosure, + holder, + }); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::join())] + pub fn join( + origin: OriginFor, + pitch: PitchId, + proof: MembershipProofOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let record = Pitches::::get(pitch).ok_or(Error::::PitchMissing)?; + ensure!(Self::is_live(&record), Error::::NotLive); + + if let Some(root) = record.membership_root { + ensure!( + T::Verifier::verify(&who, &root, proof.as_slice()), + Error::::ProofInvalid + ); + } + + Joined::::insert(pitch, &who, ()); + Self::deposit_event(Event::Joined { pitch, who }); + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::amend())] + pub fn amend( + origin: OriginFor, + pitch: PitchId, + local_tax: Option, + membership_root: Option>, + end: Option>, + residue: Option>>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Pitches::::try_mutate(pitch, |maybe_record| { + let record = maybe_record.as_mut().ok_or(Error::::PitchMissing)?; + ensure!(record.holder == who, Error::::NotHolder); + ensure!(Self::is_live(record), Error::::NotLive); + + if let Some(local_tax) = local_tax { + record.local_tax = local_tax; + } + if let Some(membership_root) = membership_root { + record.membership_root = membership_root; + } + if let Some(new_end) = end { + ensure!(record.start < new_end, Error::::BadWindow); + record.end = new_end; + Expiring::::try_mutate( + new_end.saturating_add(T::GracePeriod::get()), + |ids| { + if !ids.contains(&pitch) { + ids.try_push(pitch) + .map_err(|_| Error::::ExpiryScheduleFull)?; + } + Ok::<_, DispatchError>(()) + }, + )?; + } + if let Some(residue) = residue { + record.residue = residue; + } + + Ok::<_, DispatchError>(()) + })?; + + Self::deposit_event(Event::Amended { pitch }); + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::dissolve())] + pub fn dissolve(origin: OriginFor, pitch: PitchId) -> DispatchResult { + let who = ensure_signed(origin)?; + let record = Pitches::::get(pitch).ok_or(Error::::PitchMissing)?; + ensure!(record.holder == who, Error::::NotHolder); + Self::remove_pitch(pitch, &record); + Self::deposit_event(Event::Dissolved { pitch }); + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::grant_disclosure())] + pub fn grant_disclosure( + origin: OriginFor, + pitch: PitchId, + to: T::AccountId, + scope: DisclosureScope, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let record = Pitches::::get(pitch).ok_or(Error::::PitchMissing)?; + ensure!(record.holder == who, Error::::NotHolder); + + DisclosureGrants::::insert(pitch, &to, scope); + Self::deposit_event(Event::DisclosureGranted { pitch, to, scope }); + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::reap())] + pub fn reap(origin: OriginFor, pitch: PitchId) -> DispatchResult { + ensure_signed(origin)?; + let record = Pitches::::get(pitch).ok_or(Error::::PitchMissing)?; + let now = frame_system::Pallet::::block_number(); + ensure!( + now >= record.end.saturating_add(T::GracePeriod::get()), + Error::::NotLive + ); + Self::remove_pitch(pitch, &record); + Self::deposit_event(Event::Reaped { pitch }); + Ok(()) + } + } + + impl Pallet { + pub fn pitch_account(pitch: PitchId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating((pitch, b"pitch")) + } + + fn ensure_cell_matches_disclosure( + cell: &CellRef, + disclosure: Disclosure, + ) -> DispatchResult { + match (disclosure, cell) { + (Disclosure::Public, CellRef::Raw(_)) => Ok(()), + (Disclosure::Unlisted | Disclosure::Sealed, CellRef::Commitment(_)) => Ok(()), + (Disclosure::Gated, CellRef::Raw(_) | CellRef::Commitment(_)) => Ok(()), + _ => Err(Error::::BadCellRef.into()), + } + } + + fn ensure_resolution(cell: &CellRef, resolution: Option) -> DispatchResult { + match (cell, resolution) { + (CellRef::Raw(_), Some(resolution)) if resolution <= 15 => Ok(()), + (CellRef::Commitment(_), None) => Ok(()), + _ => Err(Error::::BadResolution.into()), + } + } + + fn ensure_window(start: BlockNumberFor, end: BlockNumberFor) -> DispatchResult { + let now = frame_system::Pallet::::block_number(); + ensure!(start >= now, Error::::WindowInPast); + ensure!(start < end, Error::::BadWindow); + Ok(()) + } + + fn is_live(record: &Pitch) -> bool { + let now = frame_system::Pallet::::block_number(); + record.start <= now && now < record.end + } + + fn index_pitch(pitch: PitchId, cell: &CellRef) -> DispatchResult { + if let CellRef::Raw(cell) = cell { + ByCell::::try_mutate(cell, |ids| { + ids.try_push(pitch).map_err(|_| Error::::IndexFull) + })?; + } + Ok(()) + } + + fn remove_pitch(pitch: PitchId, record: &Pitch) { + if let CellRef::Raw(cell) = record.cell { + ByCell::::mutate(cell, |ids| { + if let Some(pos) = ids.iter().position(|id| *id == pitch) { + ids.remove(pos); + } + }); + } + Pitches::::remove(pitch); + let _ = Joined::::clear_prefix(pitch, u32::MAX, None); + let _ = DisclosureGrants::::clear_prefix(pitch, u32::MAX, None); + } + } +} diff --git a/pallets/pitch/src/mock.rs b/pallets/pitch/src/mock.rs new file mode 100644 index 0000000..cf89e3f --- /dev/null +++ b/pallets/pitch/src/mock.rs @@ -0,0 +1,83 @@ +pub use crate::{self as fc_pallet_pitch, *}; +use frame::{ + deps::{frame_support::parameter_types, sp_core::H256, sp_runtime::BuildStorage}, + testing_prelude::*, +}; + +pub type AccountId = u64; +pub type BlockNumber = u64; +pub type CommunityId = u32; + +pub const HOLDER: AccountId = 1; +pub const MEMBER: AccountId = 2; +pub const OUTSIDER: AccountId = 3; +pub const CELL: u64 = 0x87283082bffffff; +pub const ROOT: H256 = H256::repeat_byte(42); + +#[frame_construct_runtime] +pub mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeHoldReason, + RuntimeFreezeReason + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system; + #[runtime::pallet_index(10)] + pub type Balances = pallet_balances; + #[runtime::pallet_index(20)] + pub type Pitch = fc_pallet_pitch; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = MockBlock; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +pub struct TestVerifier; +impl MembershipVerifier for TestVerifier { + fn verify(who: &AccountId, root: &H256, proof: &[u8]) -> bool { + *who == MEMBER && *root == ROOT && proof == b"member" + } +} + +parameter_types! { + pub PitchPalletId: PalletId = PalletId(*b"pitch___"); + pub const MaxPitchesPerCell: u32 = 4; + pub const MaxProofLen: u32 = 32; + pub const MaxResidueLen: u32 = 128; + pub const GracePeriod: BlockNumber = 2; +} + +impl Config for Test { + type WeightInfo = (); + type CommunityId = CommunityId; + type Verifier = TestVerifier; + type PalletId = PitchPalletId; + type MaxPitchesPerCell = MaxPitchesPerCell; + type MaxProofLen = MaxProofLen; + type MaxResidueLen = MaxResidueLen; + type GracePeriod = GracePeriod; +} + +pub fn new_test_ext() -> TestExternalities { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/pallets/pitch/src/tests.rs b/pallets/pitch/src/tests.rs new file mode 100644 index 0000000..c0511bb --- /dev/null +++ b/pallets/pitch/src/tests.rs @@ -0,0 +1,210 @@ +use crate::{ + mock::*, ByCell, CellRef, Disclosure, DisclosureGrants, Error, Event, Joined, Pitches, + SettlementPolicy, +}; +use frame::deps::frame_support::{assert_noop, assert_ok}; +use frame::deps::sp_core::H256; +use sp_runtime::{BoundedVec, Permill}; + +fn claim_public() { + assert_ok!(Pitch::claim( + RuntimeOrigin::signed(HOLDER), + CellRef::Raw(CELL), + Some(7), + 1, + 10, + Disclosure::Public, + Permill::from_percent(2), + None, + SettlementPolicy::Holder, + None, + None, + )); +} + +#[test] +fn public_pitch_is_claimed_and_indexed_by_cell() { + new_test_ext().execute_with(|| { + claim_public(); + + let pitch = Pitches::::get(0).unwrap(); + assert_eq!(pitch.holder, HOLDER); + assert_eq!(pitch.cell, CellRef::Raw(CELL)); + assert_eq!(ByCell::::get(CELL).as_slice(), &[0]); + + System::assert_last_event( + Event::Claimed { + pitch: 0, + disclosure: Disclosure::Public, + holder: HOLDER, + } + .into(), + ); + }); +} + +#[test] +fn committed_pitch_is_not_indexed_by_raw_cell() { + new_test_ext().execute_with(|| { + assert_ok!(Pitch::claim( + RuntimeOrigin::signed(HOLDER), + CellRef::Commitment(H256::repeat_byte(7)), + None, + 1, + 10, + Disclosure::Sealed, + Permill::zero(), + None, + SettlementPolicy::Holder, + None, + Some(BoundedVec::try_from(b"sealed-residue".to_vec()).unwrap()), + )); + + assert_eq!(ByCell::::get(CELL).len(), 0); + assert_eq!( + Pitches::::get(0).unwrap().cell, + CellRef::Commitment(H256::repeat_byte(7)) + ); + }); +} + +#[test] +fn validates_disclosure_and_resolution() { + new_test_ext().execute_with(|| { + assert_noop!( + Pitch::claim( + RuntimeOrigin::signed(HOLDER), + CellRef::Commitment(H256::repeat_byte(1)), + None, + 1, + 10, + Disclosure::Public, + Permill::zero(), + None, + SettlementPolicy::Holder, + None, + None, + ), + Error::::BadCellRef, + ); + + assert_noop!( + Pitch::claim( + RuntimeOrigin::signed(HOLDER), + CellRef::Raw(CELL), + Some(16), + 1, + 10, + Disclosure::Public, + Permill::zero(), + None, + SettlementPolicy::Holder, + None, + None, + ), + Error::::BadResolution, + ); + }); +} + +#[test] +fn gated_pitch_requires_valid_membership_proof() { + new_test_ext().execute_with(|| { + assert_ok!(Pitch::claim( + RuntimeOrigin::signed(HOLDER), + CellRef::Raw(CELL), + Some(7), + 1, + 10, + Disclosure::Gated, + Permill::zero(), + Some(ROOT), + SettlementPolicy::Holder, + None, + None, + )); + + assert_noop!( + Pitch::join( + RuntimeOrigin::signed(OUTSIDER), + 0, + BoundedVec::try_from(b"member".to_vec()).unwrap(), + ), + Error::::ProofInvalid, + ); + + assert_ok!(Pitch::join( + RuntimeOrigin::signed(MEMBER), + 0, + BoundedVec::try_from(b"member".to_vec()).unwrap(), + )); + assert!(Joined::::contains_key(0, MEMBER)); + }); +} + +#[test] +fn holder_can_amend_and_grant_disclosure() { + new_test_ext().execute_with(|| { + claim_public(); + + assert_ok!(Pitch::amend( + RuntimeOrigin::signed(HOLDER), + 0, + Some(Permill::from_percent(5)), + Some(Some(ROOT)), + Some(12), + None, + )); + assert_eq!( + Pitches::::get(0).unwrap().local_tax, + Permill::from_percent(5) + ); + assert_eq!(Pitches::::get(0).unwrap().end, 12); + + assert_ok!(Pitch::grant_disclosure( + RuntimeOrigin::signed(HOLDER), + 0, + MEMBER, + crate::DisclosureScope::Location, + )); + assert_eq!( + DisclosureGrants::::get(0, MEMBER), + Some(crate::DisclosureScope::Location) + ); + }); +} + +#[test] +fn dissolve_removes_pitch_and_public_index() { + new_test_ext().execute_with(|| { + claim_public(); + assert_ok!(Pitch::dissolve(RuntimeOrigin::signed(HOLDER), 0)); + + assert!(Pitches::::get(0).is_none()); + assert_eq!(ByCell::::get(CELL).len(), 0); + }); +} + +#[test] +fn reap_only_after_grace_period() { + new_test_ext().execute_with(|| { + claim_public(); + + System::set_block_number(11); + assert_noop!( + Pitch::reap(RuntimeOrigin::signed(OUTSIDER), 0), + Error::::NotLive, + ); + + System::set_block_number(12); + assert_ok!(Pitch::reap(RuntimeOrigin::signed(OUTSIDER), 0)); + assert!(Pitches::::get(0).is_none()); + }); +} + +#[test] +fn derives_stable_pitch_account() { + new_test_ext().execute_with(|| { + assert_eq!(Pitch::pitch_account(0), Pitch::pitch_account(0)); + }); +} diff --git a/pallets/pitch/src/weights.rs b/pallets/pitch/src/weights.rs new file mode 100644 index 0000000..bcd07a1 --- /dev/null +++ b/pallets/pitch/src/weights.rs @@ -0,0 +1,58 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +use frame::deps::{frame_support::weights::Weight, frame_system}; + +pub trait WeightInfo { + fn claim() -> Weight; + fn join() -> Weight; + fn amend() -> Weight; + fn dissolve() -> Weight; + fn grant_disclosure() -> Weight; + fn reap() -> Weight; +} + +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn claim() -> Weight { + Weight::from_parts(20_000_000, 0) + } + fn join() -> Weight { + Weight::from_parts(10_000_000, 0) + } + fn amend() -> Weight { + Weight::from_parts(15_000_000, 0) + } + fn dissolve() -> Weight { + Weight::from_parts(15_000_000, 0) + } + fn grant_disclosure() -> Weight { + Weight::from_parts(10_000_000, 0) + } + fn reap() -> Weight { + Weight::from_parts(15_000_000, 0) + } +} + +impl WeightInfo for () { + fn claim() -> Weight { + Weight::from_parts(20_000_000, 0) + } + fn join() -> Weight { + Weight::from_parts(10_000_000, 0) + } + fn amend() -> Weight { + Weight::from_parts(15_000_000, 0) + } + fn dissolve() -> Weight { + Weight::from_parts(15_000_000, 0) + } + fn grant_disclosure() -> Weight { + Weight::from_parts(10_000_000, 0) + } + fn reap() -> Weight { + Weight::from_parts(15_000_000, 0) + } +}