-
Notifications
You must be signed in to change notification settings - Fork 19
/
lib.rs
538 lines (468 loc) · 17.8 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
//! Allows transactions in alternative payment methods such as capacity
//!
//! ## Quick Links
//! - [Configuration: `Config`](Config)
//! - [Extrinsics: `Call`](Call)
//! - [Runtime API: `CapacityTransactionPaymentRuntimeApi`](../pallet_frequency_tx_payment_runtime_api/trait.CapacityTransactionPaymentRuntimeApi.html)
//! - [Custom RPC API: `CapacityPaymentApiServer`](../pallet_frequency_tx_payment_rpc/trait.CapacityPaymentApiServer.html)
//! - [Event Enum: `Event`](Event)
//! - [Error Enum: `Error`](Error)
#![doc = include_str!("../README.md")]
// Substrate macros are tripping the clippy::expect_used lint.
#![allow(clippy::expect_used)]
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
pallet_prelude::*,
traits::{IsSubType, IsType},
weights::{Weight, WeightToFee},
DefaultNoBound,
};
use frame_system::pallet_prelude::*;
use pallet_transaction_payment::{FeeDetails, InclusionFee, OnChargeTransaction};
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero},
transaction_validity::{TransactionValidity, TransactionValidityError},
FixedPointOperand, Saturating,
};
use sp_std::prelude::*;
use common_primitives::{
capacity::{Nontransferable, Replenishable},
node::UtilityProvider,
};
pub use pallet::*;
pub use weights::*;
mod payment;
pub use payment::*;
pub use types::GetStableWeight;
pub mod types;
pub mod capacity_stable_weights;
use capacity_stable_weights::CAPACITY_EXTRINSIC_BASE_WEIGHT;
/// Type aliases used for interaction with `OnChargeTransaction`.
pub(crate) type OnChargeTransactionOf<T> =
<T as pallet_transaction_payment::Config>::OnChargeTransaction;
/// Balance type alias.
pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
/// Liquidity info type alias (imbalances).
pub(crate) type LiquidityInfoOf<T> =
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::LiquidityInfo;
/// Capacity Balance type
pub(crate) type CapacityOf<T> = <T as Config>::Capacity;
/// Capacity Balance alias
pub(crate) type CapacityBalanceOf<T> = <CapacityOf<T> as Nontransferable>::Balance;
pub(crate) type ChargeCapacityBalanceOf<T> =
<<T as Config>::OnChargeCapacityTransaction as OnChargeCapacityTransaction<T>>::Balance;
/// Used to pass the initial payment info from pre- to post-dispatch.
#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
pub enum InitialPayment<T: Config> {
/// No initial fee was paid.
#[default]
Free,
/// The initial fee was payed in the native currency.
Token(LiquidityInfoOf<T>),
/// The initial fee was paid in an asset.
Capacity,
}
#[cfg(feature = "std")]
impl<T: Config> InitialPayment<T> {
pub fn is_free(&self) -> bool {
match *self {
InitialPayment::Free => true,
_ => false,
}
}
pub fn is_capacity(&self) -> bool {
match *self {
InitialPayment::Capacity => true,
_ => false,
}
}
pub fn is_token(&self) -> bool {
match *self {
InitialPayment::Token(_) => true,
_ => false,
}
}
}
impl<T: Config> sp_std::fmt::Debug for InitialPayment<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
match *self {
InitialPayment::Free => write!(f, "Nothing"),
InitialPayment::Capacity => write!(f, "Token"),
InitialPayment::Token(_) => write!(f, "Imbalance"),
}
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
#[allow(dead_code)]
#[frame_support::pallet]
pub mod pallet {
use super::*;
// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
// method.
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The overarching call type.
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
/// The type that replenishes and keeps capacity balances.
type Capacity: Replenishable + Nontransferable;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// The type that checks what transactions are capacity with their stable weights.
type CapacityCalls: GetStableWeight<<Self as Config>::RuntimeCall, Weight>;
/// Charge Capacity for transaction payments.
type OnChargeCapacityTransaction: OnChargeCapacityTransaction<Self>;
/// The maxmimum number of capacity calls that can be batched together.
#[pallet::constant]
type MaximumCapacityBatchLength: Get<u8>;
type BatchProvider: UtilityProvider<OriginFor<Self>, <Self as Config>::RuntimeCall>;
}
#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {}
#[pallet::error]
pub enum Error<T> {
/// The maximum amount of requested batched calls was exceeded
BatchedCallAmountExceedsMaximum,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Dispatch the given call as a sub_type of pay_with_capacity. Calls dispatched in this
/// fashion, if allowed, will pay with Capacity.
// The weight calculation is a temporary adjustment because overhead benchmarks do not account
// for capacity calls. We count reads and writes for a pay_with_capacity call,
// then subtract one of each for regular transactions since overhead benchmarks account for these.
#[pallet::call_index(0)]
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
let capacity_overhead = Pallet::<T>::get_capacity_overhead_weight();
let total = capacity_overhead.saturating_add(dispatch_info.weight);
(< T as Config >::WeightInfo::pay_with_capacity().saturating_add(total), dispatch_info.class)
})]
pub fn pay_with_capacity(
origin: OriginFor<T>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
ensure_signed(origin.clone())?;
call.dispatch(origin)
}
/// Dispatch the given call as a sub_type of pay_with_capacity_batch_all. Calls dispatched in this
/// fashion, if allowed, will pay with Capacity.
#[pallet::call_index(1)]
#[pallet::weight({
let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::<Vec<_>>();
let dispatch_weight = dispatch_infos.iter()
.map(|di| di.weight)
.fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight));
let capacity_overhead = Pallet::<T>::get_capacity_overhead_weight();
let total = capacity_overhead.saturating_add(dispatch_weight);
(< T as Config >::WeightInfo::pay_with_capacity_batch_all(calls.len() as u32).saturating_add(total), DispatchClass::Normal)
})]
pub fn pay_with_capacity_batch_all(
origin: OriginFor<T>,
calls: Vec<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
ensure_signed(origin.clone())?;
ensure!(
calls.len() <= T::MaximumCapacityBatchLength::get().into(),
Error::<T>::BatchedCallAmountExceedsMaximum
);
T::BatchProvider::batch_all(origin, calls)
}
}
}
impl<T: Config> Pallet<T> {
// The weight calculation is a temporary adjustment because overhead benchmarks do not account
// for capacity calls. We count reads and writes for a pay_with_capacity call,
// then subtract one of each for regular transactions since overhead benchmarks account for these.
// Storage: Msa PublicKeyToMsaId (r:1)
// Storage: Capacity CapacityLedger(r:1, w:2)
// Storage: Capacity CurrentEpoch(r:1) ? maybe cached in on_initialize
// Storage: System Account(r:1)
// Total (r: 4-1=3, w: 2-1=1)
pub fn get_capacity_overhead_weight() -> Weight {
T::DbWeight::get().reads(2).saturating_add(T::DbWeight::get().writes(1))
}
/// Compute the capacity fee for a transaction.
/// The fee is computed as the sum of the following:
/// - the weight fee, which is proportional to the weight of the transaction.
/// - the length fee, which is proportional to the length of the transaction;
/// - the base fee, which accounts for the overhead of an extrinsic.
/// NOTE: Changing CAPACITY_EXTRINSIC_BASE_WEIGHT will also change static capacity weights.
pub fn compute_capacity_fee(len: u32, extrinsic_weight: Weight) -> BalanceOf<T> {
let weight_fee = Self::weight_to_fee(extrinsic_weight);
let len_fee = Self::length_to_fee(len);
let base_fee = Self::weight_to_fee(CAPACITY_EXTRINSIC_BASE_WEIGHT);
let fee = base_fee.saturating_add(weight_fee).saturating_add(len_fee);
fee
}
/// Compute the capacity fee details for a transaction.
/// # Arguments
/// * `runtime_call` - The runtime call to be dispatched.
/// * `weight` - The weight of the transaction.
/// * `len` - The length of the transaction.
///
/// # Returns
/// `FeeDetails` - The fee details for the transaction.
pub fn compute_capacity_fee_details(
runtime_call: &<T as Config>::RuntimeCall,
dispatch_weight: &Weight,
len: u32,
) -> FeeDetails<BalanceOf<T>> {
let calls = T::CapacityCalls::get_inner_calls(runtime_call)
.expect("A collection of calls is expected at minimum one.");
let mut calls_weight_sum = Weight::zero();
for inner_call in calls {
let call_weight = T::CapacityCalls::get_stable_weight(&inner_call).unwrap_or_default();
calls_weight_sum = calls_weight_sum.saturating_add(call_weight);
}
let mut fees = FeeDetails { inclusion_fee: None, tip: Zero::zero() };
if !calls_weight_sum.is_zero() {
if let Some(weight) = calls_weight_sum.checked_add(dispatch_weight) {
let weight_fee = Self::weight_to_fee(weight);
let len_fee = Self::length_to_fee(len);
let base_fee = Self::weight_to_fee(CAPACITY_EXTRINSIC_BASE_WEIGHT);
let tip = Zero::zero();
fees = FeeDetails {
inclusion_fee: Some(InclusionFee {
base_fee,
len_fee,
adjusted_weight_fee: weight_fee,
}),
tip,
};
}
}
fees
}
/// Compute the length portion of a fee by invoking the configured `LengthToFee` impl.
pub fn length_to_fee(length: u32) -> BalanceOf<T> {
T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0))
}
/// Compute the unadjusted portion of the weight fee by invoking the configured `WeightToFee`
/// impl. Note that the input `weight` is capped by the maximum block weight before computation.
pub fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
// cap the weight to the maximum defined in runtime, otherwise it will be the
// `Bounded` maximum of its data type, which is not desired.
let capped_weight = weight.min(T::BlockWeights::get().max_block);
T::WeightToFee::weight_to_fee(&capped_weight)
}
}
/// Custom Transaction Validity Errors for ChargeFrqTransactionPayment
pub enum ChargeFrqTransactionPaymentError {
/// The call is not eligible to be paid for with Capacity
CallIsNotCapacityEligible,
/// The account key is not associated with an MSA
InvalidMsaKey,
/// The Capacity Target does not exist
TargetCapacityNotFound,
/// The minimum balance required for keys used to pay with Capacity
BelowMinDeposit,
}
/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
/// in the queue.
///
/// # Transaction Validity
///
/// This extension sets the `priority` field of `TransactionValidity` depending on the amount
/// of tip being paid per weight unit.
///
/// Operational transactions will receive an additional priority bump, so that they are normally
/// considered before regular transactions.
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ChargeFrqTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
impl ChargeFrqTransactionPaymentError {
pub fn into(self) -> TransactionValidityError {
TransactionValidityError::from(InvalidTransaction::Custom(self as u8))
}
}
impl<T: Config> ChargeFrqTransactionPayment<T>
where
BalanceOf<T>: Send + Sync + FixedPointOperand + IsType<ChargeCapacityBalanceOf<T>>,
<T as frame_system::Config>::RuntimeCall:
Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>>,
{
/// Utility construct from tip.
pub fn from(tip: BalanceOf<T>) -> Self {
Self(tip)
}
/// Return the tip as being chosen by the transaction sender.
pub fn tip(&self, call: &<T as frame_system::Config>::RuntimeCall) -> BalanceOf<T> {
match call.is_sub_type() {
Some(Call::pay_with_capacity { .. }) |
Some(Call::pay_with_capacity_batch_all { .. }) => Zero::zero(),
_ => self.0,
}
}
/// Withdraws fee from either Capacity ledger or Token account.
fn withdraw_fee(
&self,
who: &T::AccountId,
call: &<T as frame_system::Config>::RuntimeCall,
info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
len: usize,
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
match call.is_sub_type() {
Some(Call::pay_with_capacity { call }) =>
self.withdraw_capacity_fee(who, &vec![*call.clone()], len),
Some(Call::pay_with_capacity_batch_all { calls }) =>
self.withdraw_capacity_fee(who, calls, len),
_ => self.withdraw_token_fee(who, call, info, len, self.tip(call)),
}
}
/// Withdraws the transaction fee paid in Capacity using a key associated to an MSA.
fn withdraw_capacity_fee(
&self,
key: &T::AccountId,
calls: &Vec<<T as Config>::RuntimeCall>,
len: usize,
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
let mut calls_weight_sum = Weight::zero();
for call in calls {
let call_weight = T::CapacityCalls::get_stable_weight(call)
.ok_or(ChargeFrqTransactionPaymentError::CallIsNotCapacityEligible.into())?;
calls_weight_sum = calls_weight_sum.saturating_add(call_weight);
}
let fee = Pallet::<T>::compute_capacity_fee(len as u32, calls_weight_sum);
let fee = T::OnChargeCapacityTransaction::withdraw_fee(key, fee.into())?;
Ok((fee.into(), InitialPayment::Capacity))
}
/// Withdraws transaction fee paid with tokens from an.
fn withdraw_token_fee(
&self,
who: &T::AccountId,
call: &<T as frame_system::Config>::RuntimeCall,
info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
len: usize,
tip: BalanceOf<T>,
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, tip);
if fee.is_zero() {
return Ok((fee, InitialPayment::Free))
}
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::withdraw_fee(
who, call, info, fee, tip,
)
.map(|i| (fee, InitialPayment::Token(i)))
.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })
}
}
impl<T: Config> sp_std::fmt::Debug for ChargeFrqTransactionPayment<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "ChargeFrqTransactionPayment<{:?}>", self.0)
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Config> SignedExtension for ChargeFrqTransactionPayment<T>
where
<T as frame_system::Config>::RuntimeCall:
IsSubType<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<T>: Send
+ Sync
+ FixedPointOperand
+ From<u64>
+ IsType<ChargeCapacityBalanceOf<T>>
+ IsType<CapacityBalanceOf<T>>,
{
const IDENTIFIER: &'static str = "ChargeTransactionPayment";
type AccountId = T::AccountId;
type Call = <T as frame_system::Config>::RuntimeCall;
type AdditionalSigned = ();
type Pre = (
// tip
BalanceOf<T>,
Self::AccountId,
InitialPayment<T>,
);
/// Construct any additional data that should be in the signed payload of the transaction. Can
/// also perform any pre-signature-verification checks and return an error if needed.
fn additional_signed(&self) -> Result<(), TransactionValidityError> {
Ok(())
}
/// Frequently called by the transaction queue to validate all extrinsics:
fn validate(
&self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> TransactionValidity {
let (fee, _) = self.withdraw_fee(who, call, info, len)?;
let priority = pallet_transaction_payment::ChargeTransactionPayment::<T>::get_priority(
info,
len,
self.tip(call),
fee,
);
Ok(ValidTransaction { priority, ..Default::default() })
}
/// Do any pre-flight stuff for a signed transaction.
fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
let (_fee, initial_payment) = self.withdraw_fee(who, call, info, len)?;
Ok((self.tip(call), who.clone(), initial_payment))
}
/// Do any post-flight stuff for an extrinsic.
fn post_dispatch(
maybe_pre: Option<Self::Pre>,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
len: usize,
result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
if let Some((tip, who, initial_payment)) = maybe_pre {
match initial_payment {
InitialPayment::Token(already_withdrawn) => {
pallet_transaction_payment::ChargeTransactionPayment::<T>::post_dispatch(
Some((tip, who, already_withdrawn)),
info,
post_info,
len,
result,
)?;
},
InitialPayment::Capacity => {
debug_assert!(tip.is_zero(), "tip should be zero for Capacity tx.");
},
InitialPayment::Free => {
// `actual_fee` should be zero here for any signed extrinsic. It would be
// non-zero here in case of unsigned extrinsics as they don't pay fees but
// `compute_actual_fee` is not aware of them. In both cases it's fine to just
// move ahead without adjusting the fee, though, so we do nothing.
debug_assert!(tip.is_zero(), "tip should be zero if initial fee was zero.");
},
}
}
Ok(())
}
}