Transaction Fees

runtimes/weight-fee-runtime Try on playground View on GitHub

Substrate provides the transaction_payment pallet for calculating and collecting fees for executing transactions. Fees are broken down into two components:

  • Byte fee - A fee proportional to the transaction's length in bytes. The proportionality constant is a parameter in the transaction_payment pallet.
  • Weight fee - A fee calculated from the transaction's weight. Weights quantify the time spent executing the transaction. Learn more in the recipe on weights. The conversion doesn't need to be linear, although it often is. The same conversion function is applied across all transactions from all pallets in the runtime.
  • Fee Multiplier - A multiplier for the computed fee, that can change as the chain progresses. This topic is not (yet) covered further in the recipes.
total_fee = transaction_length * length_fee + weight_to_fee(total_weight)

Setting the Parameters

Each of the parameters described above is set in the transaction payment pallet's configuration trait. For example, the super-runtime sets these parameters as follows.

src: runtimes/super-runtime/src/lib.rs

parameter_types! {
	pub const TransactionByteFee: u128 = 1;
}

impl transaction_payment::Trait for Runtime {
	type Currency = balances::Module<Runtime>;
	type OnTransactionPayment = ();
	type TransactionByteFee = TransactionByteFee;
	type WeightToFee = IdentityFee<Balance>;
	type FeeMultiplierUpdate = ();
}

1 to 1 Conversion

In many cases converting weight to fees one-to-one, as shown above, will suffice and can be accomplished with IdentityFee. This approach is also taken in the node template. It is also possible to provide a type that makes a more complex calculation. Any type that implements WeightToFeePolynomial will suffice.

Linear Conversion

Another common way to convert weight to fees is linearly. When converting linearly, the weight is multiplied by a constant coefficient to determine the fee to charge. This is demonstrated in the weight-fee-runtime with the LinearWeightToFee struct.

We declare the struct with an associated type C, which will provide the coefficient.

pub struct LinearWeightToFee<C>(sp_std::marker::PhantomData<C>);

Then we implement WeightToFeePolynomial for it. When implementing this trait, your main job is to return a set of WeightToFeeCoefficients. These coefficients can have integer and fractional parts and be positive or negative. In our LinearWeightToFee there is a single integer coefficient supplied by the associated type.

impl<C> WeightToFeePolynomial for LinearWeightToFee<C>
where
	C: Get<Balance>,
{
	type Balance = Balance;

	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
		let coefficient = WeightToFeeCoefficient {
			coeff_integer: C::get(),
			coeff_frac: Perbill::zero(),
			negative: false,
			degree: 1,
		};

		// Return a smallvec of coefficients. Order does not need to match degrees
		// because each coefficient has an explicit degree annotation.
		smallvec!(coefficient)
	}
}

This struct is reusable, and works with different coefficients. Using it looks like this.

parameter_types! {
	// Used with LinearWeightToFee conversion. Leaving this constant intact when using other
	// conversion techniques is harmless.
	pub const FeeWeightRatio: u128 = 1_000;

	// --snip--
}

impl transaction_payment::Trait for Runtime {

	// Convert dispatch weight to a chargeable fee.
	type WeightToFee = LinearWeightToFee<FeeWeightRatio>;

	// --snip--
}

Quadratic Conversion

More complex polynomials can also be used. When using complex polynomials, it is unlikely that your logic will be reused among multiple chains, so it is generally not worth the overhead of making the coefficients configurable. The QuadraticWeightToFee demonstrates a 2nd-degree polynomial with hard-coded non-integer signed coefficients.

pub struct QuadraticWeightToFee;

impl WeightToFeePolynomial for QuadraticWeightToFee {
	type Balance = Balance;

	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
		let linear = WeightToFeeCoefficient {
			coeff_integer: 2,
			coeff_frac: Perbill::from_percent(40),
			negative: true,
			degree: 1,
		};
		let quadratic = WeightToFeeCoefficient {
			coeff_integer: 3,
			coeff_frac: Perbill::zero(),
			negative: false,
			degree: 2,
		};

		// Return a smallvec of coefficients. Order does not need to match degrees
		// because each coefficient has an explicit degree annotation. In fact, any
		// negative coefficients should be saved for last regardless of their degree
		// because large negative coefficients will likely cause saturation (to zero)
		// if they happen early on.
		smallvec![quadratic, linear]
	}
}

Collecting Fees

Having calculated the amount of fees due, runtime authors must decide which asset the fees should be paid in. A common choice is the use the Balances pallet, but any type that implements the Currency trait can be used.

src: runtimes/weight-fee-runtime/src/lib.rs

impl transaction_payment::Trait for Runtime {

	// A generic asset whose ID is stored in the generic_asset pallet's runtime storage
	type Currency = SpendingAssetCurrency<Self>;

	// --snip--
}