Skip to content

Commit df59acf

Browse files
authored
Make post dispatch fee consistent with a direct calculation (paritytech#6165)
* Make post dispatch fee consistent with a direct calculation * Remove unnecessary `saturated_into` calls * Add test with negative multipliers * Added regression test * Test improvements
1 parent d65e644 commit df59acf

File tree

4 files changed

+136
-29
lines changed

4 files changed

+136
-29
lines changed

frame/support/src/weights.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,24 +272,25 @@ pub struct PostDispatchInfo {
272272
impl PostDispatchInfo {
273273
/// Calculate how much (if any) weight was not used by the `Dispatchable`.
274274
pub fn calc_unspent(&self, info: &DispatchInfo) -> Weight {
275+
info.weight - self.calc_actual_weight(info)
276+
}
277+
278+
/// Calculate how much weight was actually spent by the `Dispatchable`.
279+
pub fn calc_actual_weight(&self, info: &DispatchInfo) -> Weight {
275280
if let Some(actual_weight) = self.actual_weight {
276-
if actual_weight >= info.weight {
277-
0
278-
} else {
279-
info.weight - actual_weight
280-
}
281+
actual_weight.min(info.weight)
281282
} else {
282-
0
283+
info.weight
283284
}
284285
}
285286
}
286287

287288
/// Extract the actual weight from a dispatch result if any or fall back to the default weight.
288289
pub fn extract_actual_weight(result: &DispatchResultWithPostInfo, info: &DispatchInfo) -> Weight {
289290
match result {
290-
Ok(post_info) => &post_info.actual_weight,
291-
Err(err) => &err.post_info.actual_weight,
292-
}.unwrap_or_else(|| info.weight).min(info.weight)
291+
Ok(post_info) => &post_info,
292+
Err(err) => &err.post_info,
293+
}.calc_actual_weight(info)
293294
}
294295

295296
impl From<Option<Weight>> for PostDispatchInfo {

frame/transaction-payment/src/lib.rs

Lines changed: 124 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use frame_support::{
4444
dispatch::DispatchResult,
4545
};
4646
use sp_runtime::{
47-
Fixed128, FixedPointNumber,
47+
Fixed128, FixedPointNumber, FixedPointOperand,
4848
transaction_validity::{
4949
TransactionPriority, ValidTransaction, InvalidTransaction, TransactionValidityError,
5050
TransactionValidity,
@@ -104,7 +104,9 @@ decl_module! {
104104
}
105105
}
106106

107-
impl<T: Trait> Module<T> {
107+
impl<T: Trait> Module<T> where
108+
BalanceOf<T>: FixedPointOperand
109+
{
108110
/// Query the data that we know about the fee of a given `call`.
109111
///
110112
/// This module is not and cannot be aware of the internals of a signed extension, for example
@@ -163,35 +165,63 @@ impl<T: Trait> Module<T> {
163165
) -> BalanceOf<T> where
164166
T::Call: Dispatchable<Info=DispatchInfo>,
165167
{
166-
if info.pays_fee == Pays::Yes {
168+
Self::compute_fee_raw(len, info.weight, tip, info.pays_fee)
169+
}
170+
171+
/// Compute the actual post dispatch fee for a particular transaction.
172+
///
173+
/// Identical to `compute_fee` with the only difference that the post dispatch corrected
174+
/// weight is used for the weight fee calculation.
175+
pub fn compute_actual_fee(
176+
len: u32,
177+
info: &DispatchInfoOf<T::Call>,
178+
post_info: &PostDispatchInfoOf<T::Call>,
179+
tip: BalanceOf<T>,
180+
) -> BalanceOf<T> where
181+
T::Call: Dispatchable<Info=DispatchInfo,PostInfo=PostDispatchInfo>,
182+
{
183+
Self::compute_fee_raw(len, post_info.calc_actual_weight(info), tip, info.pays_fee)
184+
}
185+
186+
fn compute_fee_raw(
187+
len: u32,
188+
weight: Weight,
189+
tip: BalanceOf<T>,
190+
pays_fee: Pays,
191+
) -> BalanceOf<T> {
192+
if pays_fee == Pays::Yes {
167193
let len = <BalanceOf<T>>::from(len);
168194
let per_byte = T::TransactionByteFee::get();
169195
let len_fee = per_byte.saturating_mul(len);
170-
let unadjusted_weight_fee = Self::weight_to_fee(info.weight);
196+
let unadjusted_weight_fee = Self::weight_to_fee(weight);
171197

172198
// the adjustable part of the fee
173199
let adjustable_fee = len_fee.saturating_add(unadjusted_weight_fee);
174200
let targeted_fee_adjustment = NextFeeMultiplier::get();
175-
let adjusted_fee = targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee.saturated_into());
201+
let adjusted_fee = targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee);
176202

177203
let base_fee = Self::weight_to_fee(T::ExtrinsicBaseWeight::get());
178-
base_fee.saturating_add(adjusted_fee.saturated_into()).saturating_add(tip)
204+
base_fee.saturating_add(adjusted_fee).saturating_add(tip)
179205
} else {
180206
tip
181207
}
182208
}
209+
}
183210

211+
impl<T: Trait> Module<T> {
184212
/// Compute the fee for the specified weight.
185213
///
186214
/// This fee is already adjusted by the per block fee adjustment factor and is therefore
187215
/// the share that the weight contributes to the overall fee of a transaction.
216+
///
217+
/// This function is generic in order to supply the contracts module with a way
218+
/// to calculate the gas price. The contracts module is not able to put the necessary
219+
/// `BalanceOf<T>` contraints on its trait. This function is not to be used by this module.
188220
pub fn weight_to_fee_with_adjustment<Balance>(weight: Weight) -> Balance where
189221
Balance: UniqueSaturatedFrom<u128>
190222
{
191-
let fee = UniqueSaturatedInto::<u128>::unique_saturated_into(Self::weight_to_fee(weight));
192-
UniqueSaturatedFrom::unique_saturated_from(
193-
NextFeeMultiplier::get().saturating_mul_acc_int(fee)
194-
)
223+
let fee: u128 = Self::weight_to_fee(weight).unique_saturated_into();
224+
Balance::unique_saturated_from(NextFeeMultiplier::get().saturating_mul_acc_int(fee))
195225
}
196226

197227
fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
@@ -209,7 +239,7 @@ pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(#[codec(compact)] Ba
209239

210240
impl<T: Trait + Send + Sync> ChargeTransactionPayment<T> where
211241
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
212-
BalanceOf<T>: Send + Sync,
242+
BalanceOf<T>: Send + Sync + FixedPointOperand,
213243
{
214244
/// utility constructor. Used only in client/factory code.
215245
pub fn from(fee: BalanceOf<T>) -> Self {
@@ -258,14 +288,14 @@ impl<T: Trait + Send + Sync> sp_std::fmt::Debug for ChargeTransactionPayment<T>
258288
}
259289

260290
impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> where
261-
BalanceOf<T>: Send + Sync + From<u64>,
291+
BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
262292
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
263293
{
264294
const IDENTIFIER: &'static str = "ChargeTransactionPayment";
265295
type AccountId = T::AccountId;
266296
type Call = T::Call;
267297
type AdditionalSigned = ();
268-
type Pre = (BalanceOf<T>, Self::AccountId, Option<NegativeImbalanceOf<T>>);
298+
type Pre = (BalanceOf<T>, Self::AccountId, Option<NegativeImbalanceOf<T>>, BalanceOf<T>);
269299
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) }
270300

271301
fn validate(
@@ -291,20 +321,26 @@ impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> whe
291321
info: &DispatchInfoOf<Self::Call>,
292322
len: usize
293323
) -> Result<Self::Pre, TransactionValidityError> {
294-
let (_, imbalance) = self.withdraw_fee(who, info, len)?;
295-
Ok((self.0, who.clone(), imbalance))
324+
let (fee, imbalance) = self.withdraw_fee(who, info, len)?;
325+
Ok((self.0, who.clone(), imbalance, fee))
296326
}
297327

298328
fn post_dispatch(
299329
pre: Self::Pre,
300330
info: &DispatchInfoOf<Self::Call>,
301331
post_info: &PostDispatchInfoOf<Self::Call>,
302-
_len: usize,
332+
len: usize,
303333
_result: &DispatchResult,
304334
) -> Result<(), TransactionValidityError> {
305-
let (tip, who, imbalance) = pre;
335+
let (tip, who, imbalance, fee) = pre;
306336
if let Some(payed) = imbalance {
307-
let refund = Module::<T>::weight_to_fee_with_adjustment(post_info.calc_unspent(info));
337+
let actual_fee = Module::<T>::compute_actual_fee(
338+
len as u32,
339+
info,
340+
post_info,
341+
tip,
342+
);
343+
let refund = fee.saturating_sub(actual_fee);
308344
let actual_payment = match T::Currency::deposit_into_existing(&who, refund) {
309345
Ok(refund_imbalance) => {
310346
// The refund cannot be larger than the up front payed max weight.
@@ -789,6 +825,39 @@ mod tests {
789825
});
790826
}
791827

828+
#[test]
829+
fn compute_fee_works_with_negative_multiplier() {
830+
ExtBuilder::default()
831+
.base_weight(100)
832+
.byte_fee(10)
833+
.balance_factor(0)
834+
.build()
835+
.execute_with(||
836+
{
837+
// Add a next fee multiplier
838+
NextFeeMultiplier::put(Fixed128::saturating_from_rational(-1, 2)); // = -1/2 = -.5
839+
// Base fee is unaffected by multiplier
840+
let dispatch_info = DispatchInfo {
841+
weight: 0,
842+
class: DispatchClass::Operational,
843+
pays_fee: Pays::Yes,
844+
};
845+
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 0), 100);
846+
847+
// Everything works together :)
848+
let dispatch_info = DispatchInfo {
849+
weight: 123,
850+
class: DispatchClass::Operational,
851+
pays_fee: Pays::Yes,
852+
};
853+
// 123 weight, 456 length, 100 base
854+
// adjustable fee = (123 * 1) + (456 * 10) = 4683
855+
// adjusted fee = 4683 - (4683 * -.5) = 4683 - 2341.5 = 4683 - 2341 = 2342
856+
// final fee = 100 + 2342 + 789 tip = 3231
857+
assert_eq!(Module::<Runtime>::compute_fee(456, &dispatch_info, 789), 3231);
858+
});
859+
}
860+
792861
#[test]
793862
fn compute_fee_does_not_overflow() {
794863
ExtBuilder::default()
@@ -906,4 +975,41 @@ mod tests {
906975
assert_eq!(System::events().len(), 0);
907976
});
908977
}
978+
979+
#[test]
980+
fn refund_consistent_with_actual_weight() {
981+
ExtBuilder::default()
982+
.balance_factor(10)
983+
.base_weight(7)
984+
.build()
985+
.execute_with(||
986+
{
987+
let info = info_from_weight(100);
988+
let post_info = post_info_from_weight(33);
989+
let prev_balance = Balances::free_balance(2);
990+
let len = 10;
991+
let tip = 5;
992+
993+
NextFeeMultiplier::put(Fixed128::saturating_from_rational(1, 4));
994+
995+
let pre = ChargeTransactionPayment::<Runtime>::from(tip)
996+
.pre_dispatch(&2, CALL, &info, len)
997+
.unwrap();
998+
999+
ChargeTransactionPayment::<Runtime>
1000+
::post_dispatch(pre, &info, &post_info, len, &Ok(()))
1001+
.unwrap();
1002+
1003+
let refund_based_fee = prev_balance - Balances::free_balance(2);
1004+
let actual_fee = Module::<Runtime>
1005+
::compute_actual_fee(len as u32, &info, &post_info, tip);
1006+
1007+
// 33 weight, 10 length, 7 base
1008+
// adjustable fee = (33 * 1) + (10 * 1) = 43
1009+
// adjusted fee = 43 + (43 * .25) = 43 + 10.75 = 43 + 10 = 53
1010+
// final fee = 7 + 53 + 5 tip = 65
1011+
assert_eq!(actual_fee, 65);
1012+
assert_eq!(refund_based_fee, actual_fee);
1013+
});
1014+
}
9091015
}

primitives/arithmetic/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ mod per_things;
4040
mod fixed;
4141
mod rational128;
4242

43-
pub use fixed::{FixedPointNumber, Fixed64, Fixed128};
43+
pub use fixed::{FixedPointNumber, Fixed64, Fixed128, FixedPointOperand};
4444
pub use per_things::{PerThing, Percent, PerU16, Permill, Perbill, Perquintill};
4545
pub use rational128::Rational128;
4646

primitives/runtime/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub use sp_core::RuntimeDebug;
7272
/// Re-export top-level arithmetic stuff.
7373
pub use sp_arithmetic::{
7474
Perquintill, Perbill, Permill, Percent, PerU16, Rational128, Fixed64, Fixed128,
75-
PerThing, traits::SaturatedConversion, FixedPointNumber,
75+
PerThing, traits::SaturatedConversion, FixedPointNumber, FixedPointOperand,
7676
};
7777
/// Re-export 128 bit helpers.
7878
pub use sp_arithmetic::helpers_128bit;

0 commit comments

Comments
 (0)