Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 18f0be7

Browse files
authored
Do a refund based on the actual weight (#5584)
This refunds weight and the weight bases fee back to the sender of an extrinsic after the dispatch.
1 parent 512f1c8 commit 18f0be7

4 files changed

Lines changed: 131 additions & 60 deletions

File tree

bin/node/executor/tests/basic.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -342,11 +342,6 @@ fn full_native_block_import_works() {
342342
)),
343343
topics: vec![],
344344
},
345-
EventRecord {
346-
phase: Phase::ApplyExtrinsic(1),
347-
event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(fees * 8 / 10)),
348-
topics: vec![],
349-
},
350345
EventRecord {
351346
phase: Phase::ApplyExtrinsic(1),
352347
event: Event::pallet_balances(pallet_balances::RawEvent::Transfer(
@@ -356,6 +351,11 @@ fn full_native_block_import_works() {
356351
)),
357352
topics: vec![],
358353
},
354+
EventRecord {
355+
phase: Phase::ApplyExtrinsic(1),
356+
event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(fees * 8 / 10)),
357+
topics: vec![],
358+
},
359359
EventRecord {
360360
phase: Phase::ApplyExtrinsic(1),
361361
event: Event::frame_system(frame_system::RawEvent::ExtrinsicSuccess(
@@ -395,11 +395,6 @@ fn full_native_block_import_works() {
395395
)),
396396
topics: vec![],
397397
},
398-
EventRecord {
399-
phase: Phase::ApplyExtrinsic(1),
400-
event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(fees * 8 / 10)),
401-
topics: vec![],
402-
},
403398
EventRecord {
404399
phase: Phase::ApplyExtrinsic(1),
405400
event: Event::pallet_balances(
@@ -413,14 +408,14 @@ fn full_native_block_import_works() {
413408
},
414409
EventRecord {
415410
phase: Phase::ApplyExtrinsic(1),
416-
event: Event::frame_system(frame_system::RawEvent::ExtrinsicSuccess(
417-
DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true }
418-
)),
411+
event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(fees * 8 / 10)),
419412
topics: vec![],
420413
},
421414
EventRecord {
422-
phase: Phase::ApplyExtrinsic(2),
423-
event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(fees * 8 / 10)),
415+
phase: Phase::ApplyExtrinsic(1),
416+
event: Event::frame_system(frame_system::RawEvent::ExtrinsicSuccess(
417+
DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true }
418+
)),
424419
topics: vec![],
425420
},
426421
EventRecord {
@@ -434,6 +429,11 @@ fn full_native_block_import_works() {
434429
),
435430
topics: vec![],
436431
},
432+
EventRecord {
433+
phase: Phase::ApplyExtrinsic(2),
434+
event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(fees * 8 / 10)),
435+
topics: vec![],
436+
},
437437
EventRecord {
438438
phase: Phase::ApplyExtrinsic(2),
439439
event: Event::frame_system(frame_system::RawEvent::ExtrinsicSuccess(

frame/support/src/weights.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,21 @@ pub struct PostDispatchInfo {
139139
pub actual_weight: Option<Weight>,
140140
}
141141

142+
impl PostDispatchInfo {
143+
/// Calculate how much (if any) weight was not used by the `Dispatchable`.
144+
pub fn calc_unspent(&self, info: &DispatchInfo) -> Weight {
145+
if let Some(actual_weight) = self.actual_weight {
146+
if actual_weight >= info.weight {
147+
0
148+
} else {
149+
info.weight - actual_weight
150+
}
151+
} else {
152+
0
153+
}
154+
}
155+
}
156+
142157
impl From<Option<Weight>> for PostDispatchInfo {
143158
fn from(actual_weight: Option<Weight>) -> Self {
144159
Self {

frame/system/src/lib.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ use frame_support::{
120120
Contains, Get, ModuleToIndex, OnNewAccount, OnKilledAccount, IsDeadAccount, Happened,
121121
StoredMap, EnsureOrigin,
122122
},
123-
weights::{Weight, DispatchInfo, DispatchClass, SimpleDispatchInfo, FunctionOf}
123+
weights::{Weight, DispatchInfo, PostDispatchInfo, DispatchClass, SimpleDispatchInfo, FunctionOf}
124124
};
125125
use codec::{Encode, Decode, FullCodec, EncodeLike};
126126

@@ -1169,7 +1169,7 @@ pub fn split_inner<T, R, S>(option: Option<T>, splitter: impl FnOnce(T) -> (R, S
11691169
pub struct CheckWeight<T: Trait + Send + Sync>(PhantomData<T>);
11701170

11711171
impl<T: Trait + Send + Sync> CheckWeight<T> where
1172-
T::Call: Dispatchable<Info=DispatchInfo>
1172+
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>
11731173
{
11741174
/// Get the quota ratio of each dispatch class type. This indicates that all operational
11751175
/// dispatches can use the full capacity of any resource, while user-triggered ones can consume
@@ -1264,7 +1264,7 @@ impl<T: Trait + Send + Sync> CheckWeight<T> where
12641264
}
12651265

12661266
impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> where
1267-
T::Call: Dispatchable<Info=DispatchInfo>
1267+
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>
12681268
{
12691269
type AccountId = T::AccountId;
12701270
type Call = T::Call;
@@ -1319,7 +1319,7 @@ impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> where
13191319
fn post_dispatch(
13201320
_pre: Self::Pre,
13211321
info: &DispatchInfoOf<Self::Call>,
1322-
_post_info: &PostDispatchInfoOf<Self::Call>,
1322+
post_info: &PostDispatchInfoOf<Self::Call>,
13231323
_len: usize,
13241324
result: &DispatchResult,
13251325
) -> Result<(), TransactionValidityError> {
@@ -1329,6 +1329,14 @@ impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> where
13291329
if info.class == DispatchClass::Mandatory && result.is_err() {
13301330
Err(InvalidTransaction::BadMandatory)?
13311331
}
1332+
1333+
let unspent = post_info.calc_unspent(info);
1334+
if unspent > 0 {
1335+
AllExtrinsicsWeight::mutate(|weight| {
1336+
*weight = weight.map(|w| w.saturating_sub(unspent));
1337+
})
1338+
}
1339+
13321340
Ok(())
13331341
}
13341342
}
@@ -1624,7 +1632,7 @@ mod tests {
16241632
type Origin = ();
16251633
type Trait = ();
16261634
type Info = DispatchInfo;
1627-
type PostInfo = ();
1635+
type PostInfo = PostDispatchInfo;
16281636
fn dispatch(self, _origin: Self::Origin)
16291637
-> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
16301638
panic!("Do not use dummy implementation for dispatch.");

frame/transaction-payment/src/lib.rs

Lines changed: 88 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ use codec::{Encode, Decode};
3636
use frame_support::{
3737
decl_storage, decl_module,
3838
traits::{Currency, Get, OnUnbalanced, ExistenceRequirement, WithdrawReason, Imbalance},
39-
weights::{Weight, DispatchInfo, GetDispatchInfo},
39+
weights::{Weight, DispatchInfo, PostDispatchInfo, GetDispatchInfo},
40+
dispatch::DispatchResult,
4041
};
4142
use sp_runtime::{
4243
Fixed64,
@@ -46,7 +47,7 @@ use sp_runtime::{
4647
},
4748
traits::{
4849
Zero, Saturating, SignedExtension, SaturatedConversion, Convert, Dispatchable,
49-
DispatchInfoOf,
50+
DispatchInfoOf, PostDispatchInfoOf,
5051
},
5152
};
5253
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
@@ -102,7 +103,7 @@ decl_module! {
102103
}
103104

104105
impl<T: Trait> Module<T> where
105-
T::Call: Dispatchable<Info=DispatchInfo>,
106+
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
106107
{
107108
/// Query the data that we know about the fee of a given `call`.
108109
///
@@ -140,7 +141,8 @@ impl<T: Trait> Module<T> where
140141
pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(#[codec(compact)] BalanceOf<T>);
141142

142143
impl<T: Trait + Send + Sync> ChargeTransactionPayment<T> where
143-
T::Call: Dispatchable<Info=DispatchInfo>,
144+
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
145+
BalanceOf<T>: Send + Sync,
144146
{
145147
/// utility constructor. Used only in client/factory code.
146148
pub fn from(fee: BalanceOf<T>) -> Self {
@@ -165,22 +167,12 @@ impl<T: Trait + Send + Sync> ChargeTransactionPayment<T> where
165167
len: u32,
166168
info: &DispatchInfoOf<T::Call>,
167169
tip: BalanceOf<T>,
168-
) -> BalanceOf<T>
169-
where
170-
BalanceOf<T>: Sync + Send,
171-
{
170+
) -> BalanceOf<T> {
172171
if info.pays_fee {
173172
let len = <BalanceOf<T>>::from(len);
174173
let per_byte = T::TransactionByteFee::get();
175174
let len_fee = per_byte.saturating_mul(len);
176-
177-
let weight_fee = {
178-
// cap the weight to the maximum defined in runtime, otherwise it will be the
179-
// `Bounded` maximum of its data type, which is not desired.
180-
let capped_weight = info.weight
181-
.min(<T as frame_system::Trait>::MaximumBlockWeight::get());
182-
T::WeightToFee::convert(capped_weight)
183-
};
175+
let weight_fee = Self::compute_weight_fee(info.weight);
184176

185177
// the adjustable part of the fee
186178
let adjustable_fee = len_fee.saturating_add(weight_fee);
@@ -194,6 +186,42 @@ impl<T: Trait + Send + Sync> ChargeTransactionPayment<T> where
194186
tip
195187
}
196188
}
189+
190+
fn compute_weight_fee(weight: Weight) -> BalanceOf<T> {
191+
// cap the weight to the maximum defined in runtime, otherwise it will be the
192+
// `Bounded` maximum of its data type, which is not desired.
193+
let capped_weight = weight.min(<T as frame_system::Trait>::MaximumBlockWeight::get());
194+
T::WeightToFee::convert(capped_weight)
195+
}
196+
197+
fn withdraw_fee(
198+
&self,
199+
who: &T::AccountId,
200+
info: &DispatchInfoOf<T::Call>,
201+
len: usize,
202+
) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {
203+
let tip = self.0;
204+
let fee = Self::compute_fee(len as u32, info, tip);
205+
206+
// Only mess with balances if fee is not zero.
207+
if fee.is_zero() {
208+
return Ok((fee, None));
209+
}
210+
211+
match T::Currency::withdraw(
212+
who,
213+
fee,
214+
if tip.is_zero() {
215+
WithdrawReason::TransactionPayment.into()
216+
} else {
217+
WithdrawReason::TransactionPayment | WithdrawReason::Tip
218+
},
219+
ExistenceRequirement::KeepAlive,
220+
) {
221+
Ok(imbalance) => Ok((fee, Some(imbalance))),
222+
Err(_) => Err(InvalidTransaction::Payment.into()),
223+
}
224+
}
197225
}
198226

199227
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for ChargeTransactionPayment<T> {
@@ -209,13 +237,13 @@ impl<T: Trait + Send + Sync> sp_std::fmt::Debug for ChargeTransactionPayment<T>
209237

210238
impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> where
211239
BalanceOf<T>: Send + Sync,
212-
T::Call: Dispatchable<Info=DispatchInfo>,
240+
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
213241
{
214242
const IDENTIFIER: &'static str = "ChargeTransactionPayment";
215243
type AccountId = T::AccountId;
216244
type Call = T::Call;
217245
type AdditionalSigned = ();
218-
type Pre = ();
246+
type Pre = (BalanceOf<T>, Self::AccountId, Option<NegativeImbalanceOf<T>>);
219247
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) }
220248

221249
fn validate(
@@ -225,35 +253,55 @@ impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> whe
225253
info: &DispatchInfoOf<Self::Call>,
226254
len: usize,
227255
) -> TransactionValidity {
228-
// pay any fees.
229-
let tip = self.0;
230-
let fee = Self::compute_fee(len as u32, info, tip);
231-
// Only mess with balances if fee is not zero.
232-
if !fee.is_zero() {
233-
let imbalance = match T::Currency::withdraw(
234-
who,
235-
fee,
236-
if tip.is_zero() {
237-
WithdrawReason::TransactionPayment.into()
238-
} else {
239-
WithdrawReason::TransactionPayment | WithdrawReason::Tip
240-
},
241-
ExistenceRequirement::KeepAlive,
242-
) {
243-
Ok(imbalance) => imbalance,
244-
Err(_) => return InvalidTransaction::Payment.into(),
245-
};
246-
let imbalances = imbalance.split(tip);
247-
T::OnTransactionPayment::on_unbalanceds(Some(imbalances.0).into_iter()
248-
.chain(Some(imbalances.1)));
249-
}
256+
let (fee, _) = self.withdraw_fee(who, info, len)?;
250257

251258
let mut r = ValidTransaction::default();
252259
// NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which
253260
// will be a bit more than setting the priority to tip. For now, this is enough.
254261
r.priority = fee.saturated_into::<TransactionPriority>();
255262
Ok(r)
256263
}
264+
265+
fn pre_dispatch(
266+
self,
267+
who: &Self::AccountId,
268+
_call: &Self::Call,
269+
info: &DispatchInfoOf<Self::Call>,
270+
len: usize
271+
) -> Result<Self::Pre, TransactionValidityError> {
272+
let (_, imbalance) = self.withdraw_fee(who, info, len)?;
273+
Ok((self.0, who.clone(), imbalance))
274+
}
275+
276+
fn post_dispatch(
277+
pre: Self::Pre,
278+
info: &DispatchInfoOf<Self::Call>,
279+
post_info: &PostDispatchInfoOf<Self::Call>,
280+
_len: usize,
281+
_result: &DispatchResult,
282+
) -> Result<(), TransactionValidityError> {
283+
let (tip, who, imbalance) = pre;
284+
if let Some(payed) = imbalance {
285+
let refund = Self::compute_weight_fee(post_info.calc_unspent(info));
286+
let actual_payment = match T::Currency::deposit_into_existing(&who, refund) {
287+
Ok(refund_imbalance) => {
288+
// The refund cannot be larger than the up front payed max weight.
289+
// `PostDispatchInfo::calc_unspent` guards against such a case.
290+
match payed.offset(refund_imbalance) {
291+
Ok(actual_payment) => actual_payment,
292+
Err(_) => return Err(InvalidTransaction::Payment.into()),
293+
}
294+
}
295+
// We do not recreate the account using the refund. The up front payment
296+
// is gone in that case.
297+
Err(_) => payed,
298+
};
299+
let imbalances = actual_payment.split(tip);
300+
T::OnTransactionPayment::on_unbalanceds(Some(imbalances.0).into_iter()
301+
.chain(Some(imbalances.1)));
302+
}
303+
Ok(())
304+
}
257305
}
258306

259307
#[cfg(test)]

0 commit comments

Comments
 (0)