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