Skip to content

Commit

Permalink
docs(calc): Updated README for calc package (#1386)
Browse files Browse the repository at this point in the history
* moved fn docs from README to inline

* changed add and sub operations to saturating_*

* updated copyright
  • Loading branch information
bee344 committed Feb 1, 2024
1 parent 90b89de commit cc80227
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 212 deletions.
75 changes: 1 addition & 74 deletions calc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,80 +36,7 @@ and Sidecar's [block service](https://github.com/paritytech/substrate-api-sideca
In order to build the rust source code with `wasm-pack`, please run `sh build.sh`.
This will require `wasm-pack` being installed globally. The script will install it for you, but before it does it will ask you whether you want it installed or not.

### calc_partial_fee
Tool to calculate an extrinsics' `partial_fee` (i.e. the total fee minus any tip).
It uses the following formula:

```
partial_fee = base_fee + len_fee + ((adjusted_weight_fee/estimated_weight)*actual_weight)
```

Where:
- `base_fee` is a fixed base fee to include some transaction in a block. It accounts
for the work needed to verify the signature and the computing work common to any tx.
It is constant for any tx.
- `len_fee` is a fee paid based on the size (length in bytes) of the transaction.
Longer transactions require more storage, and therefore are more expensive.
- `adjusted_weight_fee` is a fee that is itself `estimated_weight * targeted_fee_adjustment`:
- `targeted_fee_adjustment` is some adjustment made based on the network load and
other circumstantial factors, and is an opaque internal value we have no access to.
- `estimated_weight` is the "pre-dispatch" weight of the transaction. It's set
based on the cost of processing the transaction on reference hardware.
- `actual_weight` is the weight that is found in the `ExtrinsicSuccess` event for
the extrinsic in a block (it's just called `weight` in the event), and it's
value is often close to `estimated_weight`, but the node has the opportunity
to change it depending on the actual computing work necessary to process the tx.

The RPC endpoint `payment_queryFeeDetails` returns `base_fee`, `len_fee` and
`adjusted_weight_fee`. The RPC endpoint `payment_queryInfo` returns `estimated_weight`
(called `weight` in the response), and a `partialFee` value, which is our best
guess at the inclusion fee for the tx without actually submitting it and seeing
whether the node changes the weight or decides not to take a fee at all.

To get the correct values for some extrinsic from both endpoints, provide the
extrinsic bytes, and the number of the block **before the block it is included in**
(e.g. if the extrinsic was in block 100, you'd use block 99 as an argument). This
is very important.

Once you've called these endpoints, access the `ExtrinsicSuccess` event to find
the `actual_weight`, but also a `paysFee` value which signals whether the extrinsic
actually incurred a fee at all or not (a node has the opportunity to refund the
fee entirely).

With all of those values at hand, the equation above calculates the correct Fee.
Why? Well, the basic way to calculate a pre-dispatch fee is:

```
partial_fee = base_fee + len_fee + adjusted_weight_fee
```

We can do this from just the RPC methods. But then once it's in a block, we need
to swap out the weight used to calculate that `adjusted_weight_fee` with the
actual weight that was used from the `ExtrinsicSuccess` event. In the end, the
calculation itself is simple, but gathering the details needed is the main difficulty.
We do this all in Rust simply to limit any precision loss.

### calc_payout

This is a tool to calculate the payout of a staking era, either for a validator
or a nominator. This is not a predictive estimation, instead it intakes data
from a concluded [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes)
to arrive to the final amount. For this it takes the following parameters:
- `total_reward_points` are the total [era points](https://wiki.polkadot.network/docs/maintain-guides-validator-payout#era-points)
for a determined [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes).
- `era_payout` is the [payout](https://wiki.polkadot.network/docs/maintain-guides-validator-payout#payout-scheme)
for a determined [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes).
- `validator_reward_points` are the [era points](https://wiki.polkadot.network/docs/maintain-guides-validator-payout#era-points)
earned by the validator in a determined [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes).
- `validator_commission` is the commission that the validator takes of its assigned
payout before distribituing the remainder between itself and it's nominators.
- `nominator_exposure` is the amount staked by the nominator or validator,
depending on who we are inquiring about.
- `total_exposure` the total amount staked.
- `is_validator` is a `bool` that states whether the inquired account is a validator.


## Contributing

We welcome [contributions for documentation and code](https://github.com/paritytech/substrate-api-sidecar/pulls).
If you have any questions you can reach the maintainers by [filing an issue on github.](https://github.com/paritytech/substrate-api-sidecar/issues)
If you have any questions you can reach the maintainers by [filing an issue on github.](https://github.com/paritytech/substrate-api-sidecar/issues)
62 changes: 58 additions & 4 deletions calc/src/calc_partial_fee.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd. (admin@parity.io)
// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,59 @@
use sp_arithmetic::Perbill;
use wasm_bindgen::prelude::*;

/// ### calc_partial_fee
///
/// Tool to calculate an extrinsics' `partial_fee` (i.e. the total fee minus any tip).
/// It uses the following formula:
///
/// ```
/// partial_fee = base_fee + len_fee + ((adjusted_weight_fee/estimated_weight)*actual_weight)
/// ```
///
/// Where:
/// - `base_fee` is a fixed base fee to include some transaction in a block. It accounts
/// for the work needed to verify the signature and the computing work common to any tx.
/// It is constant for any tx.
/// - `len_fee` is a fee paid based on the size (length in bytes) of the transaction.
/// Longer transactions require more storage, and therefore are more expensive.
/// - `adjusted_weight_fee` is a fee that is itself `estimated_weight * targeted_fee_adjustment`:
/// - `targeted_fee_adjustment` is some adjustment made based on the network load and
/// other circumstantial factors, and is an opaque internal value we have no access to.
/// - `estimated_weight` is the "pre-dispatch" weight of the transaction. It's set
/// based on the cost of processing the transaction on reference hardware.
/// - `actual_weight` is the weight that is found in the `ExtrinsicSuccess` event for
/// the extrinsic in a block (it's just called `weight` in the event), and it's
/// value is often close to `estimated_weight`, but the node has the opportunity
/// to change it depending on the actual computing work necessary to process the tx.
///
/// The RPC endpoint `payment_queryFeeDetails` returns `base_fee`, `len_fee` and
/// `adjusted_weight_fee`. The RPC endpoint `payment_queryInfo` returns `estimated_weight`
/// (called `weight` in the response), and a `partialFee` value, which is our best
/// guess at the inclusion fee for the tx without actually submitting it and seeing
/// whether the node changes the weight or decides not to take a fee at all.
///
/// To get the correct values for some extrinsic from both endpoints, provide the
/// extrinsic bytes, and the number of the block **before the block it is included in**
/// (e.g. if the extrinsic was in block 100, you'd use block 99 as an argument). This
/// is very important.
///
/// Once you've called these endpoints, access the `ExtrinsicSuccess` event to find
/// the `actual_weight`, but also a `paysFee` value which signals whether the extrinsic
/// actually incurred a fee at all or not (a node has the opportunity to refund the
/// fee entirely).
///
/// With all of those values at hand, the equation above calculates the correct Fee.
/// Why? Well, the basic way to calculate a pre-dispatch fee is:
///
/// ```
/// partial_fee = base_fee + len_fee + adjusted_weight_fee
/// ```
///
/// We can do this from just the RPC methods. But then once it's in a block, we need
/// to swap out the weight used to calculate that `adjusted_weight_fee` with the
/// actual weight that was used from the `ExtrinsicSuccess` event. In the end, the
/// calculation itself is simple, but gathering the details needed is the main difficulty.
/// We do this all in Rust simply to limit any precision loss.
#[wasm_bindgen]
pub fn calc_partial_fee(
base_fee: &str,
Expand Down Expand Up @@ -47,9 +100,10 @@ fn calc_raw(
estimated_weight: u128,
actual_weight: u128
) -> u128 {
// calculate new adjusted_weight_fee, trying to maintain precision:
let adjusted_weight_fee = Perbill::from_rational(estimated_weight, actual_weight) * adjusted_weight_fee;

// add the fee components together to get the partial/inclusion fee:
// Calculate new adjusted_weight_fee, trying to maintain precision.
let adjusted_weight_fee = Perbill::from_rational(estimated_weight, actual_weight) * adjusted_weight_fee;
// Add the fee components together to get the partial/inclusion fee and return
// the result.
base_fee.saturating_add(len_fee).saturating_add(adjusted_weight_fee)
}
65 changes: 35 additions & 30 deletions calc/src/calc_payout.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd. (admin@parity.io)
// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,6 +28,24 @@ pub struct CalcPayout {
era_payout: Balance,
}

/// ### calc_payout
///
/// This is a tool to calculate the payout of a staking era, either for a validator
/// or a nominator. This is not a predictive estimation, instead it intakes data
/// from a concluded [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes)
/// to arrive to the final amount. For this it takes the following parameters:
/// - `total_reward_points` are the total [era points](https://wiki.polkadot.network/docs/maintain-guides-validator-payout#era-points)
/// for a determined [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes).
/// - `era_payout` is the [payout](https://wiki.polkadot.network/docs/maintain-guides-validator-payout#payout-scheme)
/// for a determined [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes).
/// - `validator_reward_points` are the [era points](https://wiki.polkadot.network/docs/maintain-guides-validator-payout#era-points)
/// earned by the validator in a determined [era](https://wiki.polkadot.network/docs/kusama-parameters#periods-of-common-actions-and-attributes).
/// - `validator_commission` is the commission that the validator takes of its assigned
/// payout before distribituing the remainder between itself and it's nominators.
/// - `nominator_exposure` is the amount staked by the nominator or validator,
/// depending on who we are inquiring about.
/// - `total_exposure` the total amount staked.
/// - `is_validator` is a `bool` that states whether the inquired account is a validator.
#[wasm_bindgen]
impl CalcPayout {
pub fn from_params(total_reward_points: u32, era_payout: &str) -> Self {
Expand Down Expand Up @@ -64,47 +82,34 @@ impl CalcPayout {
is_validator,
);

/*
This is the fraction of the total reward that will be split between the
validator and its nominators
*/
// This is the fraction of the total reward that will be split between the
// validator and its nominators
let validator_total_reward_part: Perbill =
Perbill::from_rational(validator_reward_points, self.total_reward_points);

/*
Using the previous info, here we calculate the portion of the era's reward
that the nominator and its validators are entitled to
*/
let validator_total_payout: u128 = validator_total_reward_part * self.era_payout;

/*
This is the validator's commission, independent of their share of the
reward for their stake
*/
// Using the previous info, here we calculate the portion of the era's reward
// that the nominator and its validators are entitled to
let validator_total_payout: u128 = validator_total_reward_part * self.era_payout;
// This is the validator's commission, independent of their share of the
// reward for their stake
let validator_commission: Perbill = Perbill::from_percent(validator_commission);
let validator_commission_payout: u128 = validator_commission * validator_total_payout;

/*
Subtracting the validator's commission, how much is left to split between
the validator and its nominators
*/
let validator_leftover_payout: u128 = validator_total_payout - validator_commission_payout;
// Subtracting the validator's commission, how much is left to split between
// the validator and its nominators
let validator_leftover_payout: u128 = validator_total_payout.saturating_sub(validator_commission_payout);

let own_exposure: u128 = Balance::from_str(nominator_exposure).unwrap();
let total_exposure: u128 = Balance::from_str(total_exposure).unwrap();

/*
This is the portion of the validator/nominator's leftover payout that
the staker is entitled to
*/
// This is the portion of the validator/nominator's leftover payout that
// the staker is entitled to
let own_exposure_part: Perbill = Perbill::from_rational(own_exposure, total_exposure);

/*
This is the payout for the address we are interested in, depending on
whether it's a validator or a nominator
*/

// This is the payout for the address we are interested in, depending on
// whether it's a validator or a nominator
let own_staking_payout: u128 = if is_validator {
own_exposure_part * validator_leftover_payout + validator_commission_payout
(own_exposure_part * validator_leftover_payout).saturating_add(validator_commission_payout)
} else {
own_exposure_part * validator_leftover_payout
};
Expand Down
2 changes: 1 addition & 1 deletion calc/src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd. (admin@parity.io)
// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion calc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd. (admin@parity.io)
// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
Loading

0 comments on commit cc80227

Please sign in to comment.