1use codec::{Decode, Encode, MaxEncodedLen};
2use pallet_emission0_api::Emission0Api;
3use pallet_governance_api::GovernanceApi;
4use pallet_torus0_api::{NamespacePath, Torus0Api};
5use polkadot_sdk::{
6 frame_election_provider_support::Get,
7 frame_support::{
8 DebugNoBound,
9 dispatch::DispatchResult,
10 ensure,
11 traits::{Currency, ExistenceRequirement},
12 },
13 polkadot_sdk_frame::prelude::BlockNumberFor,
14 sp_runtime::{BoundedVec, DispatchError, Percent, traits::Saturating},
15 sp_tracing::{debug_span, warn},
16};
17use scale_info::{TypeInfo, prelude::vec::Vec};
18
19use crate::AccountIdOf;
20
21#[derive(DebugNoBound, Encode, Decode, MaxEncodedLen, TypeInfo)]
28#[scale_info(skip_type_params(T))]
29pub struct Agent<T: crate::Config> {
30 pub key: AccountIdOf<T>,
32 pub name: BoundedVec<u8, T::MaxAgentNameLengthConstraint>,
33 pub url: BoundedVec<u8, T::MaxAgentUrlLengthConstraint>,
34 pub metadata: BoundedVec<u8, T::MaxAgentMetadataLengthConstraint>,
35 pub weight_penalty_factor: Percent,
38 pub registration_block: BlockNumberFor<T>,
39 pub fees: crate::fee::ValidatorFee<T>,
40 pub last_update_block: BlockNumberFor<T>,
41}
42
43pub fn register<T: crate::Config>(
51 agent_key: AccountIdOf<T>,
52 name: Vec<u8>,
53 url: Vec<u8>,
54 metadata: Vec<u8>,
55) -> DispatchResult {
56 let span = debug_span!("register", agent.key = ?agent_key);
57 let _guard = span.enter();
58
59 ensure!(
60 <T as crate::pallet::Config>::Governance::can_register_agent(&agent_key),
61 crate::pallet::Error::<T>::AgentsFrozen
62 );
63
64 ensure!(
65 !exists::<T>(&agent_key) && crate::Pallet::<T>::find_agent_by_name(&name).is_none(),
66 crate::Error::<T>::AgentAlreadyRegistered
67 );
68
69 ensure!(
70 crate::RegistrationsThisBlock::<T>::get() < crate::MaxRegistrationsPerBlock::<T>::get(),
71 crate::Error::<T>::TooManyAgentRegistrationsThisBlock
72 );
73
74 let burn_config = crate::BurnConfig::<T>::get();
75 ensure!(
76 crate::RegistrationsThisInterval::<T>::get() < burn_config.max_registrations_per_interval,
77 crate::Error::<T>::TooManyAgentRegistrationsThisInterval
78 );
79
80 let namespace_path = NamespacePath::new_agent_root(&name).map_err(|err| {
81 warn!("{agent_key:?} tried using invalid name: {err:?}");
82 crate::Error::<T>::InvalidNamespacePath
83 })?;
84
85 validate_agent_url::<T>(&url[..])?;
86 validate_agent_metadata::<T>(&metadata[..])?;
87
88 let burn = crate::Burn::<T>::get();
89
90 <T as crate::Config>::Currency::transfer(
92 &agent_key,
93 &<T as crate::Config>::Governance::dao_treasury_address(),
94 burn,
95 ExistenceRequirement::AllowDeath,
96 )
97 .map_err(|_| crate::Error::<T>::NotEnoughBalanceToRegisterAgent)?;
98
99 let registration_block = <polkadot_sdk::frame_system::Pallet<T>>::block_number();
100 crate::Agents::<T>::insert(
101 agent_key.clone(),
102 Agent {
103 key: agent_key.clone(),
104 name: BoundedVec::truncate_from(name),
105 url: BoundedVec::truncate_from(url),
106 metadata: BoundedVec::truncate_from(metadata),
107 weight_penalty_factor: Percent::from_percent(0),
108 registration_block,
109 fees: Default::default(),
110 last_update_block: registration_block,
111 },
112 );
113
114 crate::namespace::create_namespace::<T>(
115 crate::namespace::NamespaceOwnership::Account(agent_key.clone()),
116 namespace_path,
117 )?;
118
119 crate::RegistrationsThisBlock::<T>::mutate(|value| value.saturating_add(1));
120 crate::RegistrationsThisInterval::<T>::mutate(|value| value.saturating_add(1));
121
122 crate::Pallet::<T>::deposit_event(crate::Event::<T>::AgentRegistered(agent_key.clone()));
123
124 if let Some(allocator) = <T::Governance>::get_allocators().next() {
125 let _ = <T::Emission>::delegate_weight_control(&agent_key, &allocator);
126 } else {
127 polkadot_sdk::sp_tracing::warn!("no allocators available to delegate to for {agent_key:?}");
128 }
129
130 Ok(())
131}
132
133pub fn deregister<T: crate::Config>(agent_key: AccountIdOf<T>) -> DispatchResult {
136 let span = debug_span!("deregister", agent.key = ?agent_key);
137 let _guard = span.enter();
138
139 let agent = crate::Agents::<T>::get(&agent_key).ok_or(crate::Error::<T>::AgentDoesNotExist)?;
140
141 let namespace_path = NamespacePath::new_agent_root(&agent.name)
142 .map_err(|_| crate::Error::<T>::InvalidNamespacePath)?;
143 crate::namespace::delete_namespace::<T>(
144 crate::namespace::NamespaceOwnership::Account(agent_key.clone()),
145 namespace_path,
146 )?;
147
148 crate::stake::clear_key::<T>(&agent_key)?;
149
150 crate::Agents::<T>::remove(&agent_key);
151
152 crate::Pallet::<T>::deposit_event(crate::Event::<T>::AgentUnregistered(agent_key));
153
154 Ok(())
155}
156
157pub fn update<T: crate::Config>(
159 agent_key: AccountIdOf<T>,
160 url: Vec<u8>,
161 metadata: Option<Vec<u8>>,
162 staking_fee: Option<Percent>,
163 weight_control_fee: Option<Percent>,
164) -> DispatchResult {
165 let span = debug_span!("update", agent.key = ?agent_key);
166 let _guard = span.enter();
167
168 crate::Agents::<T>::try_mutate(&agent_key, |agent| {
169 let Some(agent) = agent else {
170 return Err(crate::Error::<T>::AgentDoesNotExist.into());
171 };
172
173 if is_in_update_cooldown::<T>(&agent_key)? {
174 return Err(crate::Error::<T>::AgentUpdateOnCooldown.into());
175 }
176
177 validate_agent_url::<T>(&url[..])?;
178 agent.url = BoundedVec::truncate_from(url);
179
180 if let Some(metadata) = metadata {
181 validate_agent_metadata::<T>(&metadata[..])?;
182 agent.metadata = BoundedVec::truncate_from(metadata);
183 }
184
185 let constraints = crate::FeeConstraints::<T>::get();
186
187 if let Some(staking_fee) = staking_fee {
188 ensure!(
189 staking_fee >= constraints.min_staking_fee,
190 crate::Error::<T>::InvalidStakingFee
191 );
192
193 agent.fees.staking_fee = staking_fee;
194 }
195
196 if let Some(weight_control_fee) = weight_control_fee {
197 ensure!(
198 weight_control_fee >= constraints.min_weight_control_fee,
199 crate::Error::<T>::InvalidWeightControlFee
200 );
201
202 agent.fees.weight_control_fee = weight_control_fee;
203 }
204
205 Ok::<(), DispatchError>(())
206 })?;
207
208 set_in_cooldown::<T>(&agent_key)?;
209 crate::Pallet::<T>::deposit_event(crate::Event::<T>::AgentUpdated(agent_key));
210
211 Ok(())
212}
213
214fn is_in_update_cooldown<T: crate::Config>(key: &AccountIdOf<T>) -> Result<bool, DispatchError> {
215 let current_block = <polkadot_sdk::frame_system::Pallet<T>>::block_number();
216 let cooldown = crate::AgentUpdateCooldown::<T>::get();
217
218 let last_update = crate::Agents::<T>::get(key)
219 .ok_or(crate::Error::<T>::AgentDoesNotExist)?
220 .last_update_block;
221
222 Ok(last_update.saturating_add(cooldown) > current_block)
223}
224
225fn set_in_cooldown<T: crate::Config>(key: &AccountIdOf<T>) -> DispatchResult {
226 crate::Agents::<T>::mutate(key, |agent| {
227 let Some(agent) = agent else {
228 return Err(crate::Error::<T>::AgentDoesNotExist.into());
229 };
230
231 agent.last_update_block = <polkadot_sdk::frame_system::Pallet<T>>::block_number();
232
233 Ok(())
234 })
235}
236
237pub fn exists<T: crate::Config>(key: &AccountIdOf<T>) -> bool {
238 crate::Agents::<T>::contains_key(key)
239}
240
241fn validate_agent_url<T: crate::Config>(bytes: &[u8]) -> DispatchResult {
242 let len: u32 = bytes
243 .len()
244 .try_into()
245 .map_err(|_| crate::Error::<T>::AgentUrlTooLong)?;
246
247 ensure!(len > 0, crate::Error::<T>::AgentUrlTooShort);
248
249 ensure!(
250 len <= (crate::MaxAgentUrlLength::<T>::get() as u32)
251 .min(T::MaxAgentUrlLengthConstraint::get()),
252 crate::Error::<T>::AgentUrlTooLong
253 );
254
255 ensure!(
256 core::str::from_utf8(bytes).is_ok(),
257 crate::Error::<T>::InvalidAgentUrl
258 );
259
260 Ok(())
261}
262
263fn validate_agent_metadata<T: crate::Config>(bytes: &[u8]) -> DispatchResult {
264 let len: u32 = bytes
265 .len()
266 .try_into()
267 .map_err(|_| crate::Error::<T>::AgentMetadataTooLong)?;
268
269 ensure!(len > 0, crate::Error::<T>::AgentMetadataTooShort);
270
271 ensure!(
272 len <= T::MaxAgentMetadataLengthConstraint::get(),
273 crate::Error::<T>::AgentMetadataTooLong
274 );
275
276 ensure!(
277 core::str::from_utf8(bytes).is_ok(),
278 crate::Error::<T>::InvalidAgentMetadata
279 );
280
281 Ok(())
282}
283
284#[doc(hidden)]
285pub enum PruningStrategy {
286 LeastProductive,
289 IgnoreImmunity,
292}