pallet_governance/
application.rs1use codec::{Decode, Encode, MaxEncodedLen};
2use polkadot_sdk::{
3 frame_election_provider_support::Get,
4 frame_support::{dispatch::DispatchResult, traits::Currency, DebugNoBound},
5 polkadot_sdk_frame::prelude::BlockNumberFor,
6 sp_runtime::{traits::Saturating, BoundedVec},
7 sp_std::vec::Vec,
8};
9use scale_info::TypeInfo;
10
11use crate::{
12 frame::traits::ExistenceRequirement, whitelist, AccountIdOf, AgentApplications, BalanceOf,
13};
14
15#[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)]
19#[scale_info(skip_type_params(T))]
20pub struct AgentApplication<T: crate::Config> {
21 pub id: u32,
22 pub payer_key: AccountIdOf<T>,
23 pub agent_key: AccountIdOf<T>,
24 pub data: BoundedVec<u8, T::MaxApplicationDataLength>,
25 pub cost: BalanceOf<T>,
26 pub expires_at: BlockNumberFor<T>,
27 pub action: ApplicationAction,
28 pub status: ApplicationStatus,
29}
30
31#[derive(DebugNoBound, Decode, Encode, TypeInfo, MaxEncodedLen, PartialEq, Eq)]
33pub enum ApplicationAction {
34 Add,
35 Remove,
36}
37
38#[derive(DebugNoBound, Decode, Encode, TypeInfo, MaxEncodedLen, PartialEq, Eq)]
39pub enum ApplicationStatus {
40 Open,
41 Resolved {
44 accepted: bool,
45 },
46 Expired,
47}
48
49impl<T: crate::Config> AgentApplication<T> {
50 pub fn is_open(&self) -> bool {
53 matches!(self.status, ApplicationStatus::Open)
54 }
55}
56
57pub fn submit_application<T: crate::Config>(
61 payer: AccountIdOf<T>,
62 agent_key: AccountIdOf<T>,
63 data: Vec<u8>,
64 removing: bool,
65) -> DispatchResult {
66 if !removing && whitelist::is_whitelisted::<T>(&agent_key) {
67 return Err(crate::Error::<T>::AlreadyWhitelisted.into());
68 } else if removing && !whitelist::is_whitelisted::<T>(&agent_key) {
69 return Err(crate::Error::<T>::NotWhitelisted.into());
70 }
71
72 let action = if removing {
73 ApplicationAction::Remove
74 } else {
75 ApplicationAction::Add
76 };
77
78 if exists_for_agent_key::<T>(&agent_key, &action) {
79 return Err(crate::Error::<T>::ApplicationKeyAlreadyUsed.into());
80 }
81
82 let config = crate::GlobalGovernanceConfig::<T>::get();
83 let cost = config.agent_application_cost;
84
85 <T as crate::Config>::Currency::transfer(
86 &payer,
87 &crate::DaoTreasuryAddress::<T>::get(),
88 cost,
89 ExistenceRequirement::AllowDeath,
90 )
91 .map_err(|_| crate::Error::<T>::NotEnoughBalanceToApply)?;
92
93 let data_len: u32 = data
94 .len()
95 .try_into()
96 .map_err(|_| crate::Error::<T>::InvalidApplicationDataLength)?;
97
98 let data_range = T::MinApplicationDataLength::get()..T::MaxApplicationDataLength::get();
99 if !data_range.contains(&data_len) {
100 return Err(crate::Error::<T>::InvalidApplicationDataLength.into());
101 }
102
103 let expires_at = <polkadot_sdk::frame_system::Pallet<T>>::block_number()
104 .saturating_add(config.agent_application_expiration);
105
106 let next_id = AgentApplications::<T>::iter()
107 .count()
108 .try_into()
109 .map_err(|_| crate::Error::<T>::InternalError)?;
110
111 let application = AgentApplication::<T> {
112 id: next_id,
113 payer_key: payer,
114 agent_key,
115 data: BoundedVec::truncate_from(data),
116 cost,
117 expires_at,
118 status: ApplicationStatus::Open,
119 action,
120 };
121
122 crate::AgentApplications::<T>::insert(next_id, application);
123 crate::Pallet::<T>::deposit_event(crate::Event::<T>::ApplicationCreated(next_id));
124
125 Ok(())
126}
127
128pub fn accept_application<T: crate::Config>(application_id: u32) -> DispatchResult {
131 let application = crate::AgentApplications::<T>::get(application_id)
132 .ok_or(crate::Error::<T>::ApplicationNotFound)?;
133
134 if !application.is_open() {
135 return Err(crate::Error::<T>::ApplicationNotOpen.into());
136 }
137
138 match application.action {
139 ApplicationAction::Add => {
140 crate::Whitelist::<T>::insert(application.agent_key.clone(), ());
141 crate::Pallet::<T>::deposit_event(crate::Event::<T>::WhitelistAdded(
142 application.agent_key,
143 ));
144 }
145 ApplicationAction::Remove => {
146 crate::Whitelist::<T>::remove(&application.agent_key);
147 crate::Pallet::<T>::deposit_event(crate::Event::<T>::WhitelistRemoved(
148 application.agent_key,
149 ));
150 }
151 }
152
153 crate::AgentApplications::<T>::mutate(application_id, |application| {
154 if let Some(app) = application {
155 app.status = ApplicationStatus::Resolved { accepted: true };
156 }
157 });
158
159 let _ = <T as crate::Config>::Currency::transfer(
161 &crate::DaoTreasuryAddress::<T>::get(),
162 &application.payer_key,
163 application.cost,
164 ExistenceRequirement::AllowDeath,
165 );
166
167 crate::Pallet::<T>::deposit_event(crate::Event::<T>::ApplicationAccepted(application.id));
168
169 Ok(())
170}
171
172pub fn deny_application<T: crate::Config>(application_id: u32) -> DispatchResult {
174 let application = crate::AgentApplications::<T>::get(application_id)
175 .ok_or(crate::Error::<T>::ApplicationNotFound)?;
176
177 if !application.is_open() {
178 return Err(crate::Error::<T>::ApplicationNotOpen.into());
179 }
180
181 crate::AgentApplications::<T>::mutate(application_id, |application| {
182 if let Some(app) = application {
183 app.status = ApplicationStatus::Resolved { accepted: false };
184 }
185 });
186
187 crate::Pallet::<T>::deposit_event(crate::Event::<T>::ApplicationDenied(application.id));
188
189 Ok(())
190}
191
192pub(crate) fn resolve_expired_applications<T: crate::Config>(current_block: BlockNumberFor<T>) {
196 for application in crate::AgentApplications::<T>::iter_values() {
197 if current_block < application.expires_at
198 || !matches!(application.status, ApplicationStatus::Open)
199 {
200 continue;
201 }
202
203 crate::AgentApplications::<T>::mutate(application.id, |application| {
204 if let Some(app) = application {
205 if matches!(app.status, ApplicationStatus::Open) {
206 app.status = ApplicationStatus::Expired;
207 }
208 }
209 });
210
211 crate::Pallet::<T>::deposit_event(crate::Event::<T>::ApplicationExpired(application.id));
212 }
213}
214
215pub(crate) fn exists_for_agent_key<T: crate::Config>(
217 key: &AccountIdOf<T>,
218 action: &ApplicationAction,
219) -> bool {
220 crate::AgentApplications::<T>::iter().any(|(_, application)| {
221 application.agent_key == *key
222 && application.status == ApplicationStatus::Open
223 && application.action == *action
224 })
225}