One of the most challenging parts of using the polkadot-sdk
is using generic types.
Hopefully, things like T::AccountId
have been easy enough to use, but using the Balance
type coming from NativeBalance
can be a little tricky.
The ability to use generic types in Rust is extremely powerful, but it can be hard to easily understand for those new to the polkadot-sdk
. Not to mention that polkadot-sdk
uses a lot of generic types.
The key thing to remember is that all of these generic types eventually become a concrete type. So while we are not sure while we write our pallet if T::AccountId
is [u8; 20]
, [u8; 32]
, or maybe even a String
, we know that eventually it must be one of these primitive types.
In this situation, the same can be said for the Balance
type coming from NativeBalance
. Depending on the configuration of your blockchain, and the required traits that Balance
needs to satisfy, it is perfectly valid for your Balance
type to be u32
, u64
, or u128
.
But it can only concretely be one of those, and the weird thing is that we don't know which one it is as we program our Kitties pallet!
Let's look at how we would interact with this generic type, and solve many of the issues you might encounter when trying to use it.
The Balance
type is ultimately configured inside pallet_balances
, and remember, we don't have direct access to that pallet because we used loose coupling.
The way we can access the Balance
type is through the Inspect
trait of the NativeBalance
associated type. Accessing it kind of funny, which is why we commonly introduce a BalanceOf<T>
alias type like so:
// Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface.pub type BalanceOf<T> =<<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
This is kind of a crazy line of code, so let's break it down:
Balance
type. This is what we want to access and alias.
Balance
Balance
type is part of the Inspect
trait.
Inspect::Balance
Inspect
trait is generic over AccountId
, so we need to include that.
Inspect<AccountId>::Balance
AccountId
type comes from T
, through frame_system::Config
, where the type is defined.
Inspect<<T as frame_system::Config>::AccountId>::Balance
Inspect
is accessible through the NativeBalance
associated type.
<NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance
NativeBalance
type also comes from T
, but though our pallet's own Config
.
<<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance
BalanceOf
which is generic over <T>
.
pub type BalanceOf<T> = <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance
Phew! Did you get all that? If not, don't worry too much. You can review these Rust concepts after you complete the tutorial, but there is nothing here which is specific to the polkadot-sdk
.
Why do we need this BalanceOf<T>
alias?
So that we can change this:
fn set_price(id: [u8; 32], price: <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance) {// -- snip --}
To this:
fn set_price(id: [u8; 32], price: BalanceOf<T>) {// -- snip --}
The second is way better right? This type alias handles extracting the Balance
type out of the NativeBalance
associated type every time, and all we need to do is pass the generic parameter T
.
Let's learn how we can use the BalanceOf<T>
type in our code.
I will repeat again: Because the BalanceOf<T>
type is generic, we cannot know what underlying type it is. This means we CANNOT write the following:
// This code doesn't workfn add_one(input: BalanceOf<T>) -> BalanceOf<T> {input + 1u128}
Even if we don't include u128
, we cannot write the line above. This is because that line assumes that input
must be some specific number type, and in that code, it is simply generic.
However, BalanceOf<T>
does have traits that we can use to interact with it. The key one being AtLeast32BitUnsigned
.
This means our BalanceOf<T>
must be an unsigned integer, and must be at least u32
. So it could be u32
, u64
, u128
, or even bigger if you import other crates with those larger unsigned types.
This also means we would be able to write the following:
// This code does workfn add_one(input: BalanceOf<T>) -> BalanceOf<T> {input + 1u32.into()}
We can convert any u32
into the BalanceOf<T>
type because we know at a minimum BalanceOf<T>
is AtLeast32BitUnsigned
.
Interacting between two BalanceOf<T>
types will act just like two normal numbers of the same type.
You can add them, subtract them, multiply them, divide them, and even better, do safe math operations on all of them.
let total_balance: BalanceOf<T> = balance_1.checked_add(balance_2).ok_or(ArithmeticError::Overflow)?;
We are going to use BalanceOf<T>
in the Kitty
struct to keep track if it is for sale, and the price the owner wants.
For this we can use an Option<BalanceOf<T>>
, where None
denotes that a kitty is not for sale, and Some(price)
denotes the kitty is for sale at some price
.
Now that you know how to create and use the BalanceOf<T>
type, add the type alias to your Pallet as shown in the template.
Then add a new field to the Kitty
struct called price
, which is an Option<BalanceOf<T>>
.
Finally, update the mint
function to create a new Kitty
with the new price
field set as None
.