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