1#![cfg_attr(not(feature = "std"), no_std)]
2
3pub mod agent;
4pub mod burn;
5mod ext;
6pub mod fee;
7pub mod migrations;
8pub mod namespace;
9pub mod stake;
10
11pub mod benchmarking;
12pub mod weights;
13
14pub(crate) use ext::*;
15use frame::{
16 arithmetic::Percent,
17 prelude::{ensure_root, ensure_signed},
18};
19use namespace::{NamespaceMetadata, NamespaceOwnership, NamespacePath};
20pub use pallet::*;
21use polkadot_sdk::{
22 frame_support::{
23 Identity,
24 dispatch::DispatchResult,
25 pallet_prelude::{ValueQuery, *},
26 traits::Currency,
27 },
28 frame_system::pallet_prelude::OriginFor,
29 polkadot_sdk_frame as frame, sp_std,
30};
31use scale_info::prelude::vec::Vec;
32
33use crate::{agent::Agent, burn::BurnConfiguration, fee::ValidatorFeeConstraints};
34
35#[frame::pallet]
36pub mod pallet {
37 const STORAGE_VERSION: StorageVersion = StorageVersion::new(6);
38
39 use frame::prelude::BlockNumberFor;
40 use pallet_emission0_api::Emission0Api;
41 use pallet_governance_api::GovernanceApi;
42 use pallet_permission0_api::{Permission0NamespacesApi, Permission0WalletApi, WalletScopeType};
43 use pallet_torus0_api::NamespacePathInner;
44 use polkadot_sdk::frame_support::traits::{NamedReservableCurrency, ReservableCurrency};
45 use weights::WeightInfo;
46
47 use super::*;
48
49 #[pallet::storage]
53 pub type MaxAllowedValidators<T: Config> =
54 StorageValue<_, u16, ValueQuery, T::DefaultMaxAllowedValidators>;
55
56 #[pallet::storage]
58 pub type Burn<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
59
60 #[pallet::storage]
63 pub type RegistrationsThisInterval<T: Config> = StorageValue<_, u16, ValueQuery>;
64
65 #[pallet::storage]
67 pub type MinValidatorStake<T: Config> =
68 StorageValue<_, BalanceOf<T>, ValueQuery, T::DefaultMinValidatorStake>;
69
70 #[pallet::storage]
72 pub type RewardInterval<T: Config> = StorageValue<_, u16, ValueQuery, T::DefaultRewardInterval>;
73
74 #[pallet::storage]
76 pub type Agents<T: Config> = StorageMap<_, Identity, AccountIdOf<T>, Agent<T>>;
77
78 #[pallet::storage]
80 pub type MaxNameLength<T: Config> = StorageValue<_, u16, ValueQuery, T::DefaultMaxNameLength>;
81
82 #[pallet::storage]
84 pub type MinNameLength<T: Config> = StorageValue<_, u16, ValueQuery, T::DefaultMinNameLength>;
85
86 #[pallet::storage]
88 pub type MaxAgentUrlLength<T: Config> =
89 StorageValue<_, u16, ValueQuery, T::DefaultMaxAgentUrlLength>;
90
91 #[pallet::storage]
93 pub type RegistrationsThisBlock<T> = StorageValue<_, u16, ValueQuery>;
94
95 #[pallet::storage]
98 pub type MaxRegistrationsPerBlock<T: Config> =
99 StorageValue<_, u16, ValueQuery, T::DefaultMaxRegistrationsPerBlock>;
100
101 #[pallet::storage]
104 pub type StakingTo<T: Config> =
105 StorageDoubleMap<_, Identity, T::AccountId, Identity, T::AccountId, BalanceOf<T>>;
106
107 #[pallet::storage]
110 pub type StakedBy<T: Config> =
111 StorageDoubleMap<_, Identity, T::AccountId, Identity, T::AccountId, BalanceOf<T>>;
112
113 #[pallet::storage]
115 pub type TotalStake<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
116
117 #[pallet::storage]
119 pub type MinAllowedStake<T: Config> =
120 StorageValue<_, BalanceOf<T>, ValueQuery, T::DefaultMinAllowedStake>;
121
122 #[pallet::storage]
125 pub type DividendsParticipationWeight<T: Config> =
126 StorageValue<_, Percent, ValueQuery, T::DefaultDividendsParticipationWeight>;
127
128 #[pallet::storage]
130 pub type FeeConstraints<T: Config> = StorageValue<_, ValidatorFeeConstraints<T>, ValueQuery>;
131
132 #[pallet::storage]
134 pub type BurnConfig<T: Config> = StorageValue<_, BurnConfiguration<T>, ValueQuery>;
135
136 #[pallet::storage]
138 pub type AgentUpdateCooldown<T: Config> =
139 StorageValue<_, BlockNumberFor<T>, ValueQuery, T::DefaultAgentUpdateCooldown>;
140
141 #[pallet::storage]
143 pub type Namespaces<T: Config> = StorageDoubleMap<
144 _,
145 Blake2_128Concat,
146 NamespaceOwnership<T>,
147 Blake2_128Concat,
148 NamespacePath,
149 NamespaceMetadata<T>,
150 >;
151
152 #[pallet::storage]
154 pub type NamespaceCount<T: Config> =
155 StorageMap<_, Blake2_128Concat, NamespaceOwnership<T>, u32, ValueQuery>;
156
157 #[pallet::storage]
158 pub type NamespacePricingConfig<T: Config> = StorageValue<
159 _,
160 namespace::NamespacePricingConfig<T>,
161 ValueQuery,
162 T::DefaultNamespacePricingConfig,
163 >;
164
165 #[pallet::hooks]
166 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
167 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
168 let current_block: u64 = block_number
169 .try_into()
170 .ok()
171 .expect("blockchain won't pass 2 ^ 64 blocks");
172
173 burn::adjust_burn::<T>(current_block);
174
175 RegistrationsThisBlock::<T>::set(0);
176
177 Weight::default()
178 }
179 }
180
181 #[pallet::config]
182 pub trait Config: polkadot_sdk::frame_system::Config {
183 #[pallet::constant]
184 type DefaultMaxAllowedValidators: Get<u16>;
185
186 #[pallet::constant]
187 type DefaultMinValidatorStake: Get<BalanceOf<Self>>;
188
189 #[pallet::constant]
190 type DefaultRewardInterval: Get<u16>;
191
192 #[pallet::constant]
193 type DefaultMinNameLength: Get<u16>;
194
195 #[pallet::constant]
196 type DefaultMaxNameLength: Get<u16>;
197
198 #[pallet::constant]
199 type DefaultMaxAgentUrlLength: Get<u16>;
200
201 #[pallet::constant]
202 type DefaultMaxRegistrationsPerBlock: Get<u16>;
203
204 #[pallet::constant]
205 type DefaultMinAllowedStake: Get<BalanceOf<Self>>;
206
207 #[pallet::constant]
208 type DefaultMinStakingFee: Get<u8>;
209
210 #[pallet::constant]
211 type DefaultMinWeightControlFee: Get<u8>;
212
213 #[pallet::constant]
214 type DefaultMinBurn: Get<BalanceOf<Self>>;
215
216 #[pallet::constant]
217 type DefaultMaxBurn: Get<BalanceOf<Self>>;
218
219 #[pallet::constant]
220 type DefaultAdjustmentAlpha: Get<u64>;
221
222 #[pallet::constant]
223 type DefaultTargetRegistrationsInterval: Get<BlockNumberFor<Self>>;
224
225 #[pallet::constant]
226 type DefaultTargetRegistrationsPerInterval: Get<u16>;
227
228 #[pallet::constant]
229 type DefaultMaxRegistrationsPerInterval: Get<u16>;
230
231 #[pallet::constant]
235 type MaxAgentNameLengthConstraint: Get<u32>;
236
237 #[pallet::constant]
240 type MaxAgentUrlLengthConstraint: Get<u32>;
241
242 #[pallet::constant]
243 type MaxAgentMetadataLengthConstraint: Get<u32>;
244
245 #[pallet::constant]
246 type DefaultDividendsParticipationWeight: Get<Percent>;
247
248 #[pallet::constant]
250 type DefaultAgentUpdateCooldown: Get<BlockNumberFor<Self>>;
251
252 #[pallet::constant]
253 type DefaultNamespacePricingConfig: Get<namespace::NamespacePricingConfig<Self>>;
254
255 type RuntimeEvent: From<Event<Self>>
256 + IsType<<Self as polkadot_sdk::frame_system::Config>::RuntimeEvent>;
257
258 type Currency: Currency<Self::AccountId, Balance = u128>
259 + ReservableCurrency<Self::AccountId>
260 + NamedReservableCurrency<Self::AccountId, ReserveIdentifier = [u8; 8]>
261 + Send
262 + Sync;
263 type ExistentialDeposit: Get<BalanceOf<Self>>;
264
265 type Governance: GovernanceApi<Self::AccountId>;
266
267 type Emission: Emission0Api<Self::AccountId>;
268 type Permission0: Permission0NamespacesApi<Self::AccountId, NamespacePath>
269 + Permission0WalletApi<Self::AccountId>;
270
271 type WeightInfo: WeightInfo;
272 }
273
274 #[pallet::pallet]
275 #[pallet::storage_version(STORAGE_VERSION)]
276 pub struct Pallet<T>(_);
277
278 #[pallet::call]
279 impl<T: Config> Pallet<T> {
280 #[pallet::call_index(0)]
282 #[pallet::weight((T::WeightInfo::add_stake(), DispatchClass::Normal, Pays::Yes))]
283 pub fn add_stake(
284 origin: OriginFor<T>,
285 agent_key: AccountIdOf<T>,
286 amount: BalanceOf<T>,
287 ) -> DispatchResult {
288 let key = ensure_signed(origin)?;
289 ensure!(
290 amount >= crate::MinAllowedStake::<T>::get(),
291 crate::Error::<T>::StakeTooSmall
292 );
293 stake::add_stake::<T>(key, agent_key, amount)
294 }
295
296 #[pallet::call_index(1)]
298 #[pallet::weight((T::WeightInfo::remove_stake(), DispatchClass::Normal, Pays::Yes))]
299 pub fn remove_stake(
300 origin: OriginFor<T>,
301 agent_key: AccountIdOf<T>,
302 amount: BalanceOf<T>,
303 ) -> DispatchResult {
304 let key = ensure_signed(origin)?;
305
306 ensure!(
307 !<T::Permission0>::find_active_wallet_permission(&key).any(|(_, perm)| {
308 matches!(
309 perm.r#type,
310 WalletScopeType::Stake {
311 exclusive_stake_access: true,
312 ..
313 }
314 )
315 }),
316 Error::<T>::StakeIsDelegated
317 );
318
319 stake::remove_stake::<T>(key, agent_key, amount)
320 }
321
322 #[pallet::call_index(2)]
324 #[pallet::weight((T::WeightInfo::transfer_stake(), DispatchClass::Normal, Pays::Yes))]
325 pub fn transfer_stake(
326 origin: OriginFor<T>,
327 agent_key: AccountIdOf<T>,
328 new_agent_key: AccountIdOf<T>,
329 amount: BalanceOf<T>,
330 ) -> DispatchResult {
331 let key = ensure_signed(origin)?;
332
333 ensure!(
334 !<T::Permission0>::find_active_wallet_permission(&key).any(|(_, perm)| {
335 matches!(
336 perm.r#type,
337 WalletScopeType::Stake {
338 exclusive_stake_access: true,
339 ..
340 }
341 )
342 }),
343 Error::<T>::StakeIsDelegated
344 );
345
346 stake::transfer_stake::<T>(key, agent_key, new_agent_key, amount)
347 }
348
349 #[pallet::call_index(3)]
351 #[pallet::weight((T::WeightInfo::register_agent(), DispatchClass::Normal, Pays::Yes))]
352 pub fn register_agent(
353 origin: OriginFor<T>,
354 name: Vec<u8>,
355 url: Vec<u8>,
356 metadata: Vec<u8>,
357 ) -> DispatchResult {
358 let agent_key = ensure_signed(origin)?;
359 agent::register::<T>(agent_key, name, url, metadata)
360 }
361
362 #[pallet::call_index(4)]
364 #[pallet::weight((T::WeightInfo::deregister_agent(), DispatchClass::Normal, Pays::Yes))]
365 pub fn deregister_agent(origin: OriginFor<T>) -> DispatchResult {
366 let agent_key = ensure_signed(origin)?;
367 agent::deregister::<T>(agent_key)
368 }
369
370 #[pallet::call_index(5)]
372 #[pallet::weight((T::WeightInfo::update_agent(), DispatchClass::Normal, Pays::Yes))]
373 pub fn update_agent(
374 origin: OriginFor<T>,
375 url: Vec<u8>,
376 metadata: Option<Vec<u8>>,
377 staking_fee: Option<Percent>,
378 weight_control_fee: Option<Percent>,
379 ) -> DispatchResult {
380 let agent_key = ensure_signed(origin)?;
381 agent::update::<T>(agent_key, url, metadata, staking_fee, weight_control_fee)
382 }
383
384 #[pallet::call_index(6)]
386 #[pallet::weight((T::WeightInfo::set_agent_update_cooldown(), DispatchClass::Normal, Pays::Yes))]
387 pub fn set_agent_update_cooldown(
388 origin: OriginFor<T>,
389 new_cooldown: BlockNumberFor<T>,
390 ) -> DispatchResult {
391 ensure_root(origin)?;
392 AgentUpdateCooldown::<T>::set(new_cooldown);
393 Ok(())
394 }
395
396 #[pallet::call_index(7)]
398 #[pallet::weight(Weight::default())]
399 pub fn create_namespace(origin: OriginFor<T>, path: NamespacePathInner) -> DispatchResult {
400 let owner = ensure_signed(origin)?;
401
402 ensure!(
403 <T as pallet::Config>::Governance::can_create_namespace(&owner),
404 Error::<T>::NamespacesFrozen
405 );
406
407 let namespace_path =
408 NamespacePath::new_agent(&path).map_err(|_| Error::<T>::InvalidNamespacePath)?;
409
410 namespace::create_namespace::<T>(NamespaceOwnership::Account(owner), namespace_path)
411 }
412
413 #[pallet::call_index(8)]
415 #[pallet::weight(Weight::default())]
416 pub fn delete_namespace(origin: OriginFor<T>, path: NamespacePathInner) -> DispatchResult {
417 let owner = ensure_signed(origin)?;
418
419 let namespace_path =
420 NamespacePath::new_agent(&path).map_err(|_| Error::<T>::InvalidNamespacePath)?;
421
422 ensure!(
423 !namespace_path.is_agent_root(),
424 Error::<T>::InvalidNamespacePath
425 );
426
427 namespace::delete_namespace::<T>(NamespaceOwnership::Account(owner), namespace_path)
428 }
429 }
430
431 #[pallet::event]
432 #[pallet::generate_deposit(pub fn deposit_event)]
433 pub enum Event<T: Config> {
434 StakeAdded(AccountIdOf<T>, AccountIdOf<T>, BalanceOf<T>),
437 StakeRemoved(AccountIdOf<T>, AccountIdOf<T>, BalanceOf<T>),
440 AgentRegistered(AccountIdOf<T>),
443 AgentUnregistered(AccountIdOf<T>),
446 AgentUpdated(AccountIdOf<T>),
449 NamespaceCreated {
451 owner: NamespaceOwnership<T>,
452 path: NamespacePath,
453 },
454 NamespaceDeleted {
456 owner: NamespaceOwnership<T>,
457 path: NamespacePath,
458 },
459 }
460
461 #[pallet::error]
462 pub enum Error<T> {
463 AgentDoesNotExist,
465 NotEnoughStakeToWithdraw,
467 NotEnoughBalanceToStake,
470 TooManyAgentRegistrationsThisBlock,
473 TooManyAgentRegistrationsThisInterval,
476 AgentAlreadyRegistered,
478 CouldNotConvertToBalance,
480 BalanceNotAdded,
482 StakeNotRemoved,
484 InvalidShares,
486 NotEnoughBalanceToRegisterAgent,
488 StakeNotAdded,
490 BalanceNotRemoved,
492 BalanceCouldNotBeRemoved,
494 NotEnoughStakeToRegister,
496 StillRegistered,
498 NotEnoughBalanceToTransfer,
500 InvalidAgentMetadata,
502 AgentMetadataTooLong,
504 AgentMetadataTooShort,
506 InvalidMinBurn,
508 InvalidMaxBurn,
510 AgentNameTooLong,
512 AgentNameTooShort,
514 InvalidAgentName,
516 AgentUrlTooLong,
518 AgentUrlTooShort,
520 InvalidAgentUrl,
522 AgentNameAlreadyExists,
524 StakeTooSmall,
526 AgentKeyNotWhitelisted,
529 InvalidAmount,
531 InvalidStakingFee,
533 InvalidWeightControlFee,
535 AgentUpdateOnCooldown,
537 InvalidNamespacePath,
539 NamespaceAlreadyExists,
541 NamespaceNotFound,
543 ParentNamespaceNotFound,
545 NotNamespaceOwner,
547 NamespaceHasChildren,
549 NamespaceDepthExceeded,
551 NamespaceBeingDelegated,
553 AgentsFrozen,
555 NamespacesFrozen,
557 StakeIsDelegated,
559 }
560}
561
562impl<T: Config>
563 pallet_torus0_api::Torus0Api<T::AccountId, <T::Currency as Currency<T::AccountId>>::Balance>
564 for Pallet<T>
565{
566 fn reward_interval() -> u16 {
567 RewardInterval::<T>::get()
568 }
569
570 fn min_validator_stake() -> u128 {
571 MinValidatorStake::<T>::get()
572 }
573
574 fn max_validators() -> u16 {
575 MaxAllowedValidators::<T>::get()
576 }
577
578 fn weight_control_fee(who: &T::AccountId) -> Percent {
579 Agents::<T>::get(who)
580 .map(|agent| agent.fees.weight_control_fee)
581 .unwrap_or_else(|| FeeConstraints::<T>::get().min_weight_control_fee)
582 }
583
584 fn weight_penalty_factor(who: &T::AccountId) -> Percent {
585 Agents::<T>::get(who)
586 .map(|agent| agent.weight_penalty_factor)
587 .unwrap_or_default()
588 }
589
590 fn staking_fee(who: &T::AccountId) -> Percent {
591 Agents::<T>::get(who)
592 .map(|agent| agent.fees.staking_fee)
593 .unwrap_or_else(|| FeeConstraints::<T>::get().min_staking_fee)
594 }
595
596 fn sum_staking_to(staker: &T::AccountId) -> BalanceOf<T> {
597 stake::sum_staking_to::<T>(staker)
598 }
599
600 fn staked_by(
601 staked: &T::AccountId,
602 ) -> sp_std::vec::Vec<(
603 T::AccountId,
604 <T::Currency as Currency<T::AccountId>>::Balance,
605 )> {
606 stake::get_staked_by_vector::<T>(staked)
607 }
608
609 fn stake_to(
610 staker: &T::AccountId,
611 staked: &T::AccountId,
612 amount: <T::Currency as Currency<T::AccountId>>::Balance,
613 ) -> DispatchResult {
614 stake::add_stake::<T>(staker.clone(), staked.clone(), amount)
615 }
616
617 fn remove_stake(
618 staker: &T::AccountId,
619 staked: &T::AccountId,
620 amount: <T::Currency as Currency<T::AccountId>>::Balance,
621 ) -> DispatchResult {
622 stake::remove_stake::<T>(staker.clone(), staked.clone(), amount)
623 }
624
625 fn transfer_stake(
626 staker: &T::AccountId,
627 from: &T::AccountId,
628 to: &T::AccountId,
629 amount: <T::Currency as Currency<T::AccountId>>::Balance,
630 ) -> DispatchResult {
631 stake::transfer_stake::<T>(staker.clone(), from.clone(), to.clone(), amount)
632 }
633
634 fn agent_ids() -> impl Iterator<Item = T::AccountId> {
635 Agents::<T>::iter_keys()
636 }
637
638 fn find_agent_by_name(name: &[u8]) -> Option<T::AccountId> {
639 Agents::<T>::iter()
640 .find(|(_, agent)| *agent.name == name)
641 .map(|(id, _)| id)
642 }
643
644 fn is_agent_registered(agent: &T::AccountId) -> bool {
645 Agents::<T>::contains_key(agent)
646 }
647
648 fn namespace_exists(agent: &T::AccountId, path: &NamespacePath) -> bool {
649 Namespaces::<T>::contains_key(NamespaceOwnership::Account(agent.clone()), path)
650 }
651
652 #[cfg(feature = "runtime-benchmarks")]
653 fn force_register_agent(
654 id: &T::AccountId,
655 name: Vec<u8>,
656 url: Vec<u8>,
657 metadata: Vec<u8>,
658 ) -> DispatchResult {
659 use pallet_torus0_api::NAMESPACE_AGENT_PREFIX;
660
661 crate::Agents::<T>::set(
662 id,
663 Some(Agent {
664 key: id.clone(),
665 name: name
666 .clone()
667 .try_into()
668 .map_err(|_| DispatchError::Other("failed to trim fields"))?,
669 url: url
670 .try_into()
671 .map_err(|_| DispatchError::Other("failed to trim fields"))?,
672 metadata: metadata
673 .try_into()
674 .map_err(|_| DispatchError::Other("failed to trim fields"))?,
675 weight_penalty_factor: Default::default(),
676 registration_block: Default::default(),
677 fees: Default::default(),
678 last_update_block: Default::default(),
679 }),
680 );
681 Self::force_register_namespace(id, [NAMESPACE_AGENT_PREFIX, &name].concat()).unwrap();
682
683 Ok(())
684 }
685
686 #[cfg(feature = "runtime-benchmarks")]
687 fn force_register_namespace(id: &T::AccountId, name: Vec<u8>) -> DispatchResult {
688 let name = NamespacePath::new_agent(&name).map_err(|_| Error::<T>::InvalidNamespacePath)?;
689 #[allow(deprecated)]
690 namespace::create_namespace0(NamespaceOwnership::<T>::Account(id.clone()), name, false)
691 }
692
693 #[cfg(feature = "runtime-benchmarks")]
694 fn force_set_stake(
695 staker: &T::AccountId,
696 staked: &T::AccountId,
697 amount: BalanceOf<T>,
698 ) -> DispatchResult {
699 stake::add_stake::<T>(staker.clone(), staked.clone(), amount)
700 }
701}