1#![cfg_attr(not(feature = "std"), no_std)]
2
3pub mod application;
4pub mod config;
5pub mod ext;
6pub mod migrations;
7pub mod proposal;
8pub mod roles;
9pub mod voting;
10pub mod whitelist;
11
12pub mod benchmarking;
13pub mod weights;
14
15pub(crate) use ext::*;
16use frame::prelude::ensure_root;
17pub use pallet::*;
18use polkadot_sdk::{
19 frame_support::{
20 dispatch::DispatchResult,
21 pallet_prelude::{ValueQuery, *},
22 sp_runtime::Percent,
23 traits::Currency,
24 Identity, PalletId,
25 },
26 frame_system::pallet_prelude::{ensure_signed, BlockNumberFor, OriginFor},
27 polkadot_sdk_frame::{
28 traits::AccountIdConversion,
29 {self as frame},
30 },
31 sp_std::vec::Vec,
32};
33
34use crate::{
35 application::AgentApplication,
36 config::GovernanceConfiguration,
37 proposal::{Proposal, ProposalId, UnrewardedProposal},
38};
39
40#[frame::pallet]
41pub mod pallet {
42 #![allow(clippy::too_many_arguments)]
43
44 const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
45
46 use pallet_permission0_api::{CuratorPermissions, Permission0Api, Permission0CuratorApi};
47 use polkadot_sdk::sp_core::ConstBool;
48 use proposal::GlobalParamsData;
49 use weights::WeightInfo;
50
51 use super::*;
52
53 #[pallet::storage]
55 pub type Proposals<T: Config> = StorageMap<_, Identity, ProposalId, Proposal<T>>;
56
57 #[pallet::storage]
59 pub type UnrewardedProposals<T: Config> =
60 StorageMap<_, Identity, ProposalId, UnrewardedProposal<T>>;
61
62 #[pallet::storage]
65 pub type NotDelegatingVotingPower<T: Config> =
66 StorageValue<_, BoundedBTreeSet<AccountIdOf<T>, ConstU32<{ u32::MAX }>>, ValueQuery>;
67
68 #[pallet::storage]
70 pub type GlobalGovernanceConfig<T: Config> =
71 StorageValue<_, GovernanceConfiguration<T>, ValueQuery>;
72
73 #[pallet::type_value]
75 pub fn DefaultDaoTreasuryAddress<T: Config>() -> AccountIdOf<T> {
76 <T as Config>::PalletId::get().into_account_truncating()
77 }
78
79 #[pallet::storage]
83 pub type DaoTreasuryAddress<T: Config> =
84 StorageValue<_, AccountIdOf<T>, ValueQuery, DefaultDaoTreasuryAddress<T>>;
85
86 #[pallet::storage]
88 pub type AgentApplications<T: Config> = StorageMap<_, Identity, u32, AgentApplication<T>>;
89
90 #[pallet::storage]
93 pub type Whitelist<T: Config> = StorageMap<_, Identity, AccountIdOf<T>, ()>;
94
95 #[pallet::storage]
97 pub type Allocators<T: Config> = StorageMap<_, Identity, AccountIdOf<T>, ()>;
98
99 #[pallet::storage]
102 pub type TreasuryEmissionFee<T: Config> =
103 StorageValue<_, Percent, ValueQuery, T::DefaultTreasuryEmissionFee>;
104
105 #[pallet::storage]
107 pub type AgentsFrozen<T: Config> = StorageValue<_, bool, ValueQuery, ConstBool<false>>;
108
109 #[pallet::storage]
111 pub type NamespacesFrozen<T: Config> = StorageValue<_, bool, ValueQuery, ConstBool<false>>;
112
113 #[pallet::config]
114 pub trait Config:
115 polkadot_sdk::frame_system::Config + pallet_torus0::Config + pallet_emission0::Config
116 {
117 type RuntimeEvent: From<Event<Self>>
118 + IsType<<Self as polkadot_sdk::frame_system::Config>::RuntimeEvent>;
119
120 #[pallet::constant]
121 type PalletId: Get<PalletId>;
122
123 #[pallet::constant]
124 type MinApplicationDataLength: Get<u32>;
125
126 #[pallet::constant]
127 type MaxApplicationDataLength: Get<u32>;
128
129 #[pallet::constant]
130 type ApplicationExpiration: Get<BlockNumberFor<Self>>;
131
132 #[pallet::constant]
133 type MaxPenaltyPercentage: Get<Percent>;
134
135 #[pallet::constant]
136 type DefaultTreasuryEmissionFee: Get<Percent>;
137
138 #[pallet::constant]
139 type DefaultProposalCost: Get<BalanceOf<Self>>;
140
141 #[pallet::constant]
142 type DefaultProposalExpiration: Get<BlockNumberFor<Self>>;
143
144 #[pallet::constant]
145 type DefaultAgentApplicationCost: Get<BalanceOf<Self>>;
146
147 #[pallet::constant]
148 type DefaultAgentApplicationExpiration: Get<BlockNumberFor<Self>>;
149
150 #[pallet::constant]
151 type DefaultProposalRewardTreasuryAllocation: Get<Percent>;
152
153 #[pallet::constant]
154 type DefaultMaxProposalRewardTreasuryAllocation: Get<BalanceOf<Self>>;
155
156 #[pallet::constant]
157 type DefaultProposalRewardInterval: Get<BlockNumberFor<Self>>;
158
159 type Currency: Currency<Self::AccountId, Balance = u128> + Send + Sync;
160
161 type Permission0: Permission0Api<OriginFor<Self>>
162 + Permission0CuratorApi<Self::AccountId, OriginFor<Self>, BlockNumberFor<Self>>;
163
164 type WeightInfo: WeightInfo;
165 }
166
167 #[pallet::pallet]
168 #[pallet::storage_version(STORAGE_VERSION)]
169 pub struct Pallet<T>(_);
170
171 #[pallet::hooks]
172 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
173 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
174 application::resolve_expired_applications::<T>(block_number);
175
176 proposal::tick_proposals::<T>(block_number);
177 proposal::tick_proposal_rewards::<T>(block_number);
178
179 Weight::zero()
180 }
181 }
182
183 #[pallet::call]
184 impl<T: Config> Pallet<T> {
185 #[pallet::call_index(2)]
187 #[pallet::weight((<T as Config>::WeightInfo::add_allocator(), DispatchClass::Normal, Pays::Yes))]
188 pub fn add_allocator(origin: OriginFor<T>, key: AccountIdOf<T>) -> DispatchResult {
189 ensure_root(origin)?;
190 roles::add_allocator::<T>(key)
191 }
192
193 #[pallet::call_index(3)]
196 #[pallet::weight((<T as Config>::WeightInfo::remove_allocator(), DispatchClass::Normal, Pays::Yes))]
197 pub fn remove_allocator(origin: OriginFor<T>, key: AccountIdOf<T>) -> DispatchResult {
198 ensure_root(origin)?;
199 roles::remove_allocator::<T>(key)
200 }
201
202 #[pallet::call_index(4)]
205 #[pallet::weight((<T as Config>::WeightInfo::add_to_whitelist(), DispatchClass::Normal, Pays::Yes))]
206 pub fn add_to_whitelist(origin: OriginFor<T>, key: AccountIdOf<T>) -> DispatchResult {
207 <T as Config>::Permission0::ensure_curator_permission(
208 origin,
209 CuratorPermissions::WHITELIST_MANAGE,
210 )?;
211 whitelist::add_to_whitelist::<T>(key)
212 }
213
214 #[pallet::call_index(5)]
217 #[pallet::weight((<T as Config>::WeightInfo::remove_from_whitelist(), DispatchClass::Normal, Pays::Yes))]
218 pub fn remove_from_whitelist(origin: OriginFor<T>, key: AccountIdOf<T>) -> DispatchResult {
219 <T as Config>::Permission0::ensure_curator_permission(
220 origin,
221 CuratorPermissions::WHITELIST_MANAGE,
222 )?;
223 whitelist::remove_from_whitelist::<T>(key)
224 }
225
226 #[pallet::call_index(6)]
229 #[pallet::weight((<T as Config>::WeightInfo::accept_application(), DispatchClass::Normal, Pays::Yes))]
230 pub fn accept_application(origin: OriginFor<T>, application_id: u32) -> DispatchResult {
231 <T as Config>::Permission0::ensure_curator_permission(
232 origin,
233 CuratorPermissions::APPLICATION_REVIEW,
234 )?;
235 application::accept_application::<T>(application_id)
236 }
237
238 #[pallet::call_index(7)]
241 #[pallet::weight((<T as Config>::WeightInfo::deny_application(), DispatchClass::Normal, Pays::Yes))]
242 pub fn deny_application(origin: OriginFor<T>, application_id: u32) -> DispatchResult {
243 <T as Config>::Permission0::ensure_curator_permission(
244 origin,
245 CuratorPermissions::APPLICATION_REVIEW,
246 )?;
247 application::deny_application::<T>(application_id)
248 }
249
250 #[pallet::call_index(8)]
253 #[pallet::weight((<T as Config>::WeightInfo::penalize_agent(), DispatchClass::Normal, Pays::Yes))]
254 pub fn penalize_agent(
255 origin: OriginFor<T>,
256 agent_key: AccountIdOf<T>,
257 percentage: u8,
258 ) -> DispatchResult {
259 roles::penalize_agent::<T>(origin, agent_key, percentage)
260 }
261
262 #[pallet::call_index(9)]
264 #[pallet::weight((<T as Config>::WeightInfo::submit_application(), DispatchClass::Normal, Pays::Yes))]
265 pub fn submit_application(
266 origin: OriginFor<T>,
267 agent_key: AccountIdOf<T>,
268 metadata: Vec<u8>,
269 removing: bool,
270 ) -> DispatchResult {
271 let payer = ensure_signed(origin)?;
272 application::submit_application::<T>(payer, agent_key, metadata, removing)
273 }
274
275 #[pallet::call_index(10)]
277 #[pallet::weight((<T as Config>::WeightInfo::add_global_params_proposal(), DispatchClass::Normal, Pays::Yes))]
278 pub fn add_global_params_proposal(
279 origin: OriginFor<T>,
280 data: GlobalParamsData<T>,
281 metadata: Vec<u8>,
282 ) -> DispatchResult {
283 let proposer = ensure_signed(origin)?;
284 proposal::add_global_params_proposal::<T>(proposer, data, metadata)
285 }
286
287 #[pallet::call_index(11)]
289 #[pallet::weight((<T as Config>::WeightInfo::add_global_custom_proposal(), DispatchClass::Normal, Pays::Yes))]
290 pub fn add_global_custom_proposal(
291 origin: OriginFor<T>,
292 metadata: Vec<u8>,
293 ) -> DispatchResult {
294 let proposer = ensure_signed(origin)?;
295 proposal::add_global_custom_proposal::<T>(proposer, metadata)
296 }
297
298 #[pallet::call_index(12)]
301 #[pallet::weight((<T as Config>::WeightInfo::add_dao_treasury_transfer_proposal(), DispatchClass::Normal, Pays::Yes))]
302 pub fn add_dao_treasury_transfer_proposal(
303 origin: OriginFor<T>,
304 value: BalanceOf<T>,
305 destination_key: AccountIdOf<T>,
306 data: Vec<u8>,
307 ) -> DispatchResult {
308 let proposer = ensure_signed(origin)?;
309 proposal::add_dao_treasury_transfer_proposal::<T>(
310 proposer,
311 value,
312 destination_key,
313 data,
314 )
315 }
316
317 #[pallet::call_index(13)]
319 #[pallet::weight((<T as Config>::WeightInfo::vote_proposal(), DispatchClass::Normal, Pays::Yes))]
320 pub fn vote_proposal(
321 origin: OriginFor<T>,
322 proposal_id: u64,
323 agree: bool,
324 ) -> DispatchResult {
325 let voter = ensure_signed(origin)?;
326 voting::add_vote::<T>(voter, proposal_id, agree)
327 }
328
329 #[pallet::call_index(14)]
331 #[pallet::weight((<T as Config>::WeightInfo::remove_vote_proposal(), DispatchClass::Normal, Pays::Yes))]
332 pub fn remove_vote_proposal(origin: OriginFor<T>, proposal_id: u64) -> DispatchResult {
333 let voter = ensure_signed(origin)?;
334 voting::remove_vote::<T>(voter, proposal_id)
335 }
336
337 #[pallet::call_index(15)]
339 #[pallet::weight((<T as Config>::WeightInfo::enable_vote_delegation(), DispatchClass::Normal, Pays::Yes))]
340 pub fn enable_vote_delegation(origin: OriginFor<T>) -> DispatchResult {
341 let delegator = ensure_signed(origin)?;
342 voting::enable_delegation::<T>(delegator)
343 }
344
345 #[pallet::call_index(16)]
347 #[pallet::weight((<T as Config>::WeightInfo::disable_vote_delegation(), DispatchClass::Normal, Pays::Yes))]
348 pub fn disable_vote_delegation(origin: OriginFor<T>) -> DispatchResult {
349 let delegator = ensure_signed(origin)?;
350 voting::disable_delegation::<T>(delegator)
351 }
352
353 #[pallet::call_index(17)]
355 #[pallet::weight((<T as Config>::WeightInfo::add_emission_proposal(), DispatchClass::Normal, Pays::Yes))]
356 pub fn add_emission_proposal(
357 origin: OriginFor<T>,
358 recycling_percentage: Percent,
359 treasury_percentage: Percent,
360 incentives_ratio: Percent,
361 data: Vec<u8>,
362 ) -> DispatchResult {
363 let proposer = ensure_signed(origin)?;
364 proposal::add_emission_proposal::<T>(
365 proposer,
366 recycling_percentage,
367 treasury_percentage,
368 incentives_ratio,
369 data,
370 )
371 }
372
373 #[pallet::call_index(18)]
376 #[pallet::weight((<T as Config>::WeightInfo::add_emission_proposal(), DispatchClass::Normal, Pays::No))]
377 pub fn set_emission_params(
378 origin: OriginFor<T>,
379 recycling_percentage: Percent,
380 treasury_percentage: Percent,
381 ) -> DispatchResult {
382 ensure_root(origin)?;
384
385 pallet_emission0::EmissionRecyclingPercentage::<T>::set(recycling_percentage);
386 crate::TreasuryEmissionFee::<T>::set(treasury_percentage);
387
388 Ok(())
389 }
390
391 #[pallet::call_index(19)]
392 #[pallet::weight((<T as Config>::WeightInfo::toggle_agent_freezing(), DispatchClass::Normal, Pays::No))]
393 pub fn toggle_agent_freezing(origin: OriginFor<T>) -> DispatchResult {
394 let curator = <T as pallet::Config>::Permission0::ensure_curator_permission(
395 origin,
396 CuratorPermissions::AGENT_FREEZING_TOGGLING,
397 )?;
398
399 let new_state = !crate::AgentsFrozen::<T>::get();
400 AgentsFrozen::<T>::set(new_state);
401
402 crate::Pallet::<T>::deposit_event(crate::Event::AgentFreezingToggled {
403 curator,
404 new_state,
405 });
406
407 Ok(())
408 }
409
410 #[pallet::call_index(20)]
411 #[pallet::weight((<T as Config>::WeightInfo::toggle_namespace_freezing(), DispatchClass::Normal, Pays::No))]
412 pub fn toggle_namespace_freezing(origin: OriginFor<T>) -> DispatchResult {
413 let curator = <T as pallet::Config>::Permission0::ensure_curator_permission(
414 origin,
415 CuratorPermissions::NAMESPACE_FREEZING_TOGGLING,
416 )?;
417
418 let new_state = !crate::NamespacesFrozen::<T>::get();
419 NamespacesFrozen::<T>::set(new_state);
420
421 crate::Pallet::<T>::deposit_event(crate::Event::NamespaceFreezingToggled {
422 curator,
423 new_state,
424 });
425
426 Ok(())
427 }
428 }
429
430 #[pallet::event]
431 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
432 pub enum Event<T: Config> {
433 ProposalCreated(ProposalId),
435 ProposalAccepted(ProposalId),
437 ProposalRefused(ProposalId),
439 ProposalExpired(ProposalId),
441 ProposalVoted(u64, T::AccountId, bool),
443 ProposalVoteUnregistered(u64, T::AccountId),
445 WhitelistAdded(T::AccountId),
447 WhitelistRemoved(T::AccountId),
449 ApplicationCreated(u32),
451 ApplicationAccepted(u32),
453 ApplicationDenied(u32),
455 ApplicationExpired(u32),
457 PenaltyApplied {
459 curator: T::AccountId,
460 agent: T::AccountId,
461 penalty: Percent,
462 },
463 AgentFreezingToggled {
465 curator: T::AccountId,
466 new_state: bool,
467 },
468 NamespaceFreezingToggled {
470 curator: T::AccountId,
471 new_state: bool,
472 },
473 }
474
475 #[pallet::error]
476 pub enum Error<T> {
477 ProposalIsFinished,
479 InvalidProposalFinalizationParameters,
481 InvalidProposalVotingParameters,
483 InvalidProposalCost,
486 InvalidProposalExpiration,
489 NotEnoughBalanceToPropose,
491 ProposalDataTooSmall,
493 ProposalDataTooLarge,
495 ModuleDelegatingForMaxStakers,
497 ProposalNotFound,
499 ProposalClosed,
502 InvalidProposalData,
504 InvalidCurrencyConversionValue,
506 InsufficientDaoTreasuryFunds,
508 AlreadyVoted,
510 NotVoted,
512 InsufficientStake,
514 VoterIsDelegatingVotingPower,
517 InternalError,
520 ApplicationNotOpen,
522 ApplicationKeyAlreadyUsed,
524 NotEnoughBalanceToApply,
526 NotCurator,
528 ApplicationNotFound,
530 AlreadyWhitelisted,
532 NotWhitelisted,
535 CouldNotConvertToBalance,
537 InvalidApplicationDataLength,
539 AlreadyCurator,
541 AlreadyAllocator,
543 NotAllocator,
545 AgentNotFound,
547 InvalidPenaltyPercentage,
549 InvalidMinNameLength,
551 InvalidMaxNameLength,
553 InvalidMaxAllowedWeights,
555 InvalidMinWeightControlFee,
557 InvalidMinStakingFee,
559 InvalidEmissionProposalData,
561 }
562}
563
564impl<T: Config> pallet_governance_api::GovernanceApi<T::AccountId> for Pallet<T> {
565 fn dao_treasury_address() -> T::AccountId {
566 DaoTreasuryAddress::<T>::get()
567 }
568
569 fn treasury_emission_fee() -> Percent {
570 TreasuryEmissionFee::<T>::get()
571 }
572
573 fn is_whitelisted(key: &T::AccountId) -> bool {
574 whitelist::is_whitelisted::<T>(key)
575 }
576
577 fn ensure_allocator(key: &T::AccountId) -> DispatchResult {
578 crate::roles::ensure_allocator::<T>(key)
579 }
580
581 fn get_allocators() -> impl Iterator<Item = T::AccountId> {
582 Allocators::<T>::iter_keys()
583 }
584
585 fn set_allocator(key: &T::AccountId) {
586 Allocators::<T>::insert(key, ());
587 }
588
589 fn can_create_namespace(key: &T::AccountId) -> bool {
590 !NamespacesFrozen::<T>::get() || Self::is_whitelisted(key)
591 }
592
593 fn can_register_agent(key: &T::AccountId) -> bool {
594 !AgentsFrozen::<T>::get() || Self::is_whitelisted(key)
595 }
596
597 #[cfg(feature = "runtime-benchmarks")]
598 fn force_set_whitelisted(key: &T::AccountId) {
599 Whitelist::<T>::insert(key, ());
600 }
601}