4

I am a bit confused with how to do operations with the Balances in the Substrate runtime. More concrete the division.

The example I want to implement is similar to the Lottery pallet but with shared prizes distribution:

Person A buys 10 UNITS of ticket 1.

Person B buys 10 UNITS of ticket 2.

Person C buys 30 UNITS of ticket 1.

The total Pot is 50 UNITS.

If ticket 1 is the winner, with weighted distribution the person A have to receive the 25% of the pot and the person C the 75%. The maths for this weighted distribution are simple: the amount that one deposit / the total amount of the winners deposit in the case of Person A = 10/40 = 0.25

The normal division shows a 0 when try for example 10/40:

let weighted = amount_deposit_winner / total_amount_deposit_winners;
let amount_to_receive = weighted * total_pot;

This is because you can not use floating point maths in the runtime, and you got the rounded result.

You can check this 2 answers from SE for more info:

So what I did was convert the Substrate type Balance into u128 (How do you convert between Substrate specific types and Rust primitive types?) and do the division using Perquintill as suggested here and here.

let amount_winner_in_u128 = amount_deposit_winner.saturated_into::<u128>();
let total_winners_in_u128 = total_amount_deposit_winners.saturated_into::<u128>();

let weighted = Perquintill::from_rational(amount_winner_in_u128, total_winners_in_u128);
let amount_to_receive = weighted.mul_floor(total_pot.saturated_into::<u128>());

And then transform it into Balance again in order to perform the transfer:

amount_to_receive.saturated_into()

Is this a correct way to calculate these weights for Balances in Substrate?, or is there a better way to do it?

1 Answer 1

3

Balances in most substrate chains are u128, however this is usually opaque, allowing developers to change the type if they want. Converting the value explicitly to u128 is definitely not the right thing to do.

Whenever you are dealing with fractions or percents, you probably want to start using the types found in sp-arithmetic. See:

So you probably want:

let total = 40;
let fraction = Perbill::from_rational(10, 40);
let share = fraction * total;

// share == 10

You can see similar logic in the Staking pallet where we reward users their share of the staking reward: https://github.com/paritytech/substrate/blob/master/frame/staking/src/pallet/impls.rs#L206

// Lets now calculate how this is split to the nominators.
// Reward only the clipped exposures. Note this is not necessarily sorted.
for nominator in exposure.others.iter() {
    let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total);

    let nominator_reward: BalanceOf<T> =
        nominator_exposure_part * validator_leftover_payout;
    // We can now make nominator payout:
    if let Some(imbalance) = Self::make_payout(&nominator.who, nominator_reward) {
        // Note: this logic does not count payouts for `RewardDestination::None`.
        nominator_payout_count += 1;
        let e =
            Event::<T>::Rewarded { stash: nominator.who.clone(), amount: imbalance.peek() };
        Self::deposit_event(e);
        total_imbalance.subsume(imbalance);
    }
}

You will need to make sure your logic is sound for handling all the percents and potential rounding errors.

1
  • Thanks Shawn, the Staking pallet code helps a lot. I am going to check more in detail to see how it handles the percents and potential rounding errors.
    – Alex Bean
    Commented Dec 1, 2022 at 14:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.