1use crate::{
2 generate_permission_id, get_total_allocated_percentage, pallet,
3 permission::{emission::*, *},
4 update_permission_indices, AccumulatedStreamAmounts, BalanceOf, Config, DistributionControl,
5 EmissionAllocation, EmissionScope, EnforcementTracking, Error, Event, NegativeImbalanceOf,
6 Pallet, PermissionContract, PermissionDuration, PermissionId, PermissionScope, Permissions,
7};
8
9use pallet_permission0_api::{
10 DistributionControl as ApiDistributionControl, EmissionAllocation as ApiEmissionAllocation,
11 EnforcementAuthority as ApiEnforcementAuthority, Permission0EmissionApi,
12 PermissionDuration as ApiPermissionDuration, RevocationTerms as ApiRevocationTerms, StreamId,
13};
14use polkadot_sdk::{
15 frame_support::{dispatch::DispatchResult, ensure, traits::ReservableCurrency},
16 frame_system::{self, ensure_signed, ensure_signed_or_root},
17 polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
18 sp_core::{Get, TryCollect},
19 sp_runtime::{
20 traits::{CheckedAdd, Saturating, Zero},
21 BoundedBTreeMap, DispatchError, Percent, Vec,
22 },
23};
24
25use pallet_torus0_api::Torus0Api;
26
27impl<T: Config>
28 Permission0EmissionApi<
29 T::AccountId,
30 OriginFor<T>,
31 BlockNumberFor<T>,
32 crate::BalanceOf<T>,
33 NegativeImbalanceOf<T>,
34 > for pallet::Pallet<T>
35{
36 fn grant_emission_permission(
37 grantor: T::AccountId,
38 grantee: T::AccountId,
39 allocation: ApiEmissionAllocation<crate::BalanceOf<T>>,
40 targets: Vec<(T::AccountId, u16)>,
41 distribution: ApiDistributionControl<crate::BalanceOf<T>, BlockNumberFor<T>>,
42 duration: ApiPermissionDuration<BlockNumberFor<T>>,
43 revocation: ApiRevocationTerms<T::AccountId, BlockNumberFor<T>>,
44 enforcement: ApiEnforcementAuthority<T::AccountId>,
45 ) -> Result<PermissionId, DispatchError> {
46 let internal_allocation = match allocation {
47 ApiEmissionAllocation::Streams(streams) => EmissionAllocation::Streams(
48 streams
49 .try_into()
50 .map_err(|_| crate::Error::<T>::TooManyStreams)?,
51 ),
52 ApiEmissionAllocation::FixedAmount(amount) => EmissionAllocation::FixedAmount(amount),
53 };
54
55 let internal_distribution = match distribution {
56 ApiDistributionControl::Manual => DistributionControl::Manual,
57 ApiDistributionControl::Automatic(threshold) => {
58 DistributionControl::Automatic(threshold)
59 }
60 ApiDistributionControl::AtBlock(block) => DistributionControl::AtBlock(block),
61 ApiDistributionControl::Interval(interval) => DistributionControl::Interval(interval),
62 };
63
64 let duration = super::translate_duration(duration)?;
65 let revocation = super::translate_revocation_terms(revocation)?;
66 let enforcement = super::translate_enforcement_authority(enforcement)?;
67
68 let targets = targets
69 .into_iter()
70 .try_collect()
71 .map_err(|_| crate::Error::<T>::TooManyTargets)?;
72
73 grant_emission_permission_impl::<T>(
74 grantor,
75 grantee,
76 internal_allocation,
77 targets,
78 internal_distribution,
79 duration,
80 revocation,
81 enforcement,
82 None, )
84 }
85
86 fn accumulate_emissions(
87 agent: &T::AccountId,
88 stream: &StreamId,
89 amount: &mut NegativeImbalanceOf<T>,
90 ) {
91 crate::permission::emission::do_accumulate_emissions::<T>(agent, stream, amount);
92 }
93
94 fn process_auto_distributions(current_block: BlockNumberFor<T>) {
95 crate::permission::do_auto_permission_execution::<T>(current_block);
96 }
97
98 fn get_accumulated_amount(
99 permission_id: &PermissionId,
100 stream: &StreamId,
101 ) -> crate::BalanceOf<T> {
102 let Some(contract) = Permissions::<T>::get(permission_id) else {
103 return Zero::zero();
104 };
105
106 crate::AccumulatedStreamAmounts::<T>::get((contract.grantor, stream, permission_id))
107 .unwrap_or_default()
108 }
109}
110
111#[allow(clippy::too_many_arguments)]
113pub(crate) fn grant_emission_permission_impl<T: Config>(
114 grantor: T::AccountId,
115 grantee: T::AccountId,
116 allocation: EmissionAllocation<T>,
117 targets: BoundedBTreeMap<T::AccountId, u16, T::MaxTargetsPerPermission>,
118 distribution: DistributionControl<T>,
119 duration: PermissionDuration<T>,
120 revocation: RevocationTerms<T>,
121 enforcement: EnforcementAuthority<T>,
122 parent_id: Option<PermissionId>,
123) -> Result<PermissionId, DispatchError> {
124 use polkadot_sdk::frame_support::ensure;
125
126 ensure!(
127 T::Torus::is_agent_registered(&grantor),
128 Error::<T>::NotRegisteredAgent
129 );
130 ensure!(
131 T::Torus::is_agent_registered(&grantee),
132 Error::<T>::NotRegisteredAgent
133 );
134
135 validate_emission_permission_target_weights::<T>(&targets)?;
136
137 match &allocation {
138 EmissionAllocation::Streams(streams) => {
139 validate_emission_permission_streams::<T>(streams, &grantor)?;
140 }
141 EmissionAllocation::FixedAmount(amount) => {
142 ensure!(*amount > BalanceOf::<T>::zero(), Error::<T>::InvalidAmount);
143 ensure!(
144 T::Currency::can_reserve(&grantor, *amount),
145 Error::<T>::InsufficientBalance
146 );
147 ensure!(
148 matches!(
149 &distribution,
150 DistributionControl::Manual | DistributionControl::AtBlock(_)
151 ),
152 Error::<T>::FixedAmountCanOnlyBeTriggeredOnce
153 );
154 }
155 }
156
157 validate_emission_permission_distribution::<T>(&distribution)?;
158
159 if let Some(parent) = parent_id {
160 let parent_contract =
161 Permissions::<T>::get(parent).ok_or(Error::<T>::ParentPermissionNotFound)?;
162
163 ensure!(
164 parent_contract.grantee == grantor,
165 Error::<T>::NotPermissionGrantee
166 );
167 }
168
169 let emission_scope = EmissionScope {
170 allocation: allocation.clone(),
171 distribution,
172 targets,
173 accumulating: true, };
175
176 let scope = PermissionScope::Emission(emission_scope);
177
178 let permission_id = generate_permission_id::<T>(&grantor, &grantee, &scope)?;
179
180 let contract = PermissionContract {
181 grantor: grantor.clone(),
182 grantee: grantee.clone(),
183 scope,
184 duration,
185 revocation,
186 enforcement,
187 last_execution: None,
188 execution_count: 0,
189 parent: parent_id,
190 created_at: <frame_system::Pallet<T>>::block_number(),
191 };
192
193 match allocation {
196 EmissionAllocation::FixedAmount(amount) => {
197 T::Currency::reserve(&grantor, amount)?;
198 }
199 EmissionAllocation::Streams(streams) => {
200 for stream in streams.keys() {
201 AccumulatedStreamAmounts::<T>::set(
202 (&grantor, stream, permission_id),
203 Some(Zero::zero()),
204 )
205 }
206 }
207 }
208
209 Permissions::<T>::insert(permission_id, contract);
210
211 update_permission_indices::<T>(&grantor, &grantee, permission_id)?;
212
213 <Pallet<T>>::deposit_event(Event::PermissionGranted {
214 grantor,
215 grantee,
216 permission_id,
217 });
218
219 Ok(permission_id)
220}
221
222pub fn execute_permission_impl<T: Config>(
223 permission_id: &PermissionId,
224 contract: &PermissionContract<T>,
225 emission_scope: &EmissionScope<T>,
226) -> DispatchResult {
227 match &emission_scope.distribution {
228 DistributionControl::Manual => {
229 ensure!(
230 emission_scope.accumulating,
231 Error::<T>::UnsupportedPermissionType
232 );
233
234 let accumulated = match &emission_scope.allocation {
235 EmissionAllocation::Streams(streams) => streams
236 .keys()
237 .filter_map(|id| {
238 AccumulatedStreamAmounts::<T>::get((&contract.grantor, id, permission_id))
239 })
240 .fold(BalanceOf::<T>::zero(), |acc, e| acc.saturating_add(e)), EmissionAllocation::FixedAmount(amount) => *amount,
242 };
243
244 ensure!(!accumulated.is_zero(), Error::<T>::NoAccumulatedAmount);
245
246 crate::permission::emission::do_distribute_emission::<T>(
247 *permission_id,
248 contract,
249 DistributionReason::Manual,
250 );
251
252 Ok(())
253 }
254 _ => Err(Error::<T>::InvalidDistributionMethod.into()),
255 }
256}
257
258pub fn toggle_permission_accumulation_impl<T: Config>(
260 origin: OriginFor<T>,
261 permission_id: PermissionId,
262 accumulating: bool,
263) -> DispatchResult {
264 let who = ensure_signed_or_root(origin)?;
265
266 let mut contract =
267 Permissions::<T>::get(permission_id).ok_or(Error::<T>::PermissionNotFound)?;
268
269 if let Some(who) = &who {
270 match &contract.enforcement {
271 _ if who == &contract.grantor => {}
272 EnforcementAuthority::None => {
273 return Err(Error::<T>::NotAuthorizedToToggle.into());
274 }
275 EnforcementAuthority::ControlledBy {
276 controllers,
277 required_votes,
278 } => {
279 ensure!(controllers.contains(who), Error::<T>::NotAuthorizedToToggle);
280
281 let referendum = EnforcementReferendum::EmissionAccumulation(accumulating);
282 let votes = EnforcementTracking::<T>::get(permission_id, &referendum)
283 .into_iter()
284 .filter(|id| id != who)
285 .filter(|id| controllers.contains(id))
286 .count();
287
288 if votes.saturating_add(1) < *required_votes as usize {
289 return EnforcementTracking::<T>::mutate(
290 permission_id,
291 referendum.clone(),
292 |votes| {
293 votes
294 .try_insert(who.clone())
295 .map_err(|_| Error::<T>::TooManyControllers)?;
296
297 <Pallet<T>>::deposit_event(Event::EnforcementVoteCast {
298 permission_id,
299 voter: who.clone(),
300 referendum,
301 });
302
303 Ok(())
304 },
305 );
306 }
307 }
308 }
309 }
310
311 match &mut contract.scope {
312 PermissionScope::Emission(emission_scope) => emission_scope.accumulating = accumulating,
313 _ => return Err(Error::<T>::UnsupportedPermissionType.into()),
314 }
315
316 Permissions::<T>::insert(permission_id, contract);
317
318 EnforcementTracking::<T>::remove(
320 permission_id,
321 EnforcementReferendum::EmissionAccumulation(accumulating),
322 );
323
324 <Pallet<T>>::deposit_event(Event::PermissionAccumulationToggled {
325 permission_id,
326 accumulating,
327 toggled_by: who,
328 });
329
330 Ok(())
331}
332
333pub(crate) fn update_emission_permission<T: Config>(
334 origin: OriginFor<T>,
335 permission_id: PermissionId,
336 new_targets: BoundedBTreeMap<T::AccountId, u16, T::MaxTargetsPerPermission>,
337 new_streams: Option<BoundedBTreeMap<StreamId, Percent, T::MaxStreamsPerPermission>>,
338 new_distribution_control: Option<DistributionControl<T>>,
339) -> DispatchResult {
340 let who = ensure_signed(origin)?;
341
342 let permission = Permissions::<T>::get(permission_id);
343
344 let Some(mut permission) = permission else {
345 return Err(Error::<T>::PermissionNotFound.into());
346 };
347
348 let allowed_grantor = permission.grantor == who && permission.is_updatable();
349 let allowed_grantee =
350 permission.grantee == who && (new_streams.is_none() && new_distribution_control.is_none());
351
352 if !allowed_grantor && !allowed_grantee {
353 return Err(Error::<T>::NotAuthorizedToEdit.into());
354 }
355
356 let mut scope = permission.scope.clone();
357 match &mut scope {
358 PermissionScope::Emission(emission_scope) => {
359 validate_emission_permission_target_weights::<T>(&new_targets)?;
360
361 emission_scope.targets = new_targets;
362
363 let EmissionAllocation::Streams(streams) = &mut emission_scope.allocation else {
364 return Err(Error::<T>::NotEditable.into());
365 };
366
367 if let Some(new_streams) = new_streams {
368 crate::permission::emission::do_distribute_emission::<T>(
369 permission_id,
370 &permission,
371 DistributionReason::Manual,
372 );
373
374 for stream in streams.keys() {
375 AccumulatedStreamAmounts::<T>::remove((
376 &permission.grantor,
377 stream,
378 &permission_id,
379 ));
380 }
381
382 validate_emission_permission_streams::<T>(&new_streams, &permission.grantor)?;
383
384 for stream in new_streams.keys() {
385 AccumulatedStreamAmounts::<T>::set(
386 (&permission.grantor, stream, permission_id),
387 Some(Zero::zero()),
388 )
389 }
390
391 *streams = new_streams;
392 }
393
394 if let Some(new_distribution_control) = new_distribution_control {
395 validate_emission_permission_distribution::<T>(&new_distribution_control)?;
396
397 emission_scope.distribution = new_distribution_control;
398 }
399 }
400 _ => return Err(Error::<T>::NotEditable.into()),
401 }
402
403 permission.scope = scope;
404 Permissions::<T>::set(permission_id, Some(permission));
405
406 Ok(())
407}
408
409fn validate_emission_permission_target_weights<T: Config>(
410 targets: &BoundedBTreeMap<T::AccountId, u16, T::MaxTargetsPerPermission>,
411) -> DispatchResult {
412 ensure!(!targets.is_empty(), Error::<T>::NoTargetsSpecified);
413
414 for (target, weight) in targets {
415 ensure!(*weight > 0, Error::<T>::InvalidTargetWeight);
416 ensure!(
417 T::Torus::is_agent_registered(target),
418 Error::<T>::NotRegisteredAgent
419 );
420 }
421
422 Ok(())
423}
424fn validate_emission_permission_streams<T: Config>(
425 streams: &BoundedBTreeMap<StreamId, Percent, T::MaxStreamsPerPermission>,
426 grantor: &T::AccountId,
427) -> DispatchResult {
428 for (stream, percentage) in streams {
429 ensure!(*percentage <= Percent::one(), Error::<T>::InvalidPercentage);
430
431 let total_allocated = get_total_allocated_percentage::<T>(grantor, stream);
432 let new_total_allocated = match total_allocated.checked_add(percentage) {
433 Some(new_total_allocated) => new_total_allocated,
434 None => return Err(Error::<T>::TotalAllocationExceeded.into()),
435 };
436
437 ensure!(
438 new_total_allocated <= Percent::one(),
439 Error::<T>::TotalAllocationExceeded
440 );
441 }
442
443 Ok(())
444}
445
446fn validate_emission_permission_distribution<T: Config>(
447 distribution: &DistributionControl<T>,
448) -> DispatchResult {
449 match distribution {
450 DistributionControl::Automatic(threshold) => {
451 ensure!(
452 *threshold >= T::MinAutoDistributionThreshold::get(),
453 Error::<T>::InvalidThreshold
454 );
455 }
456 DistributionControl::Interval(interval) => {
457 ensure!(!interval.is_zero(), Error::<T>::InvalidInterval);
458 }
459 DistributionControl::AtBlock(block) => {
460 let current_block = <polkadot_sdk::frame_system::Pallet<T>>::block_number();
461 ensure!(*block > current_block, Error::<T>::InvalidInterval);
462 }
463 _ => {}
464 }
465
466 Ok(())
467}