6

I am trying to put some test accounts into the storage of the following structure.

#[pallet::storage]
#[pallet::getter(fn account_collection)]
pub(super) type AccountsCollection<T: Config> =
    StorageValue<_, Vec<T::AccountId>, ValueQuery>;

In the following I have an AccountId32 type but I want a T::AccountId type. I intend to call some extrinsic which will check if the caller is in account_collection.

let account_collection = vec![sp_keyring::sr25519::Keyring::Alice.to_account_id()];
AccountsCollection::<T>::put(&account_collection);

I am new to substrate any advice or help is appreciated.

2 Answers 2

2

I don't recommend that you program into your pallet the logic you shared above.

One of the benefits of FRAME and the generic types we use is that within a pallet, we do not need to assume what the underlying type of a generic is. So when you make the assumption within your pallet that T::AccountId is AccountId32, you make your pallet incompatible with any other Substrate system which does not have this assumption.

Instead, what you should look to do is continue to abstract this kind of behavior behind generic types, and instead only define the actual accounts you want with your specific type in the runtime.

Use the Genesis Config of a Pallet

If your goal is to populate the AccountsCollection storage with some test account like Alice for testing your chain, you can create a genesis configuration where you can specify in the chain specification what accounts you want to be populated on block 0.

You can find template logic for this in the Balances pallet, where we use genesis to give users like Alice a balance at the beginning of the chain:

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
    pub balances: Vec<(T::AccountId, T::Balance)>,
}

#[cfg(feature = "std")]
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
    fn default() -> Self {
        Self { balances: Default::default() }
    }
}

#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> {
    fn build(&self) {
        let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n);
        <TotalIssuance<T, I>>::put(total);

        <StorageVersion<T, I>>::put(Releases::V2_0_0);

        for (_, balance) in &self.balances {
            assert!(
                *balance >= <T as Config<I>>::ExistentialDeposit::get(),
                "the balance of any account should always be at least the existential deposit.",
            )
        }

        // ensure no duplicates exist.
        let endowed_accounts = self
            .balances
            .iter()
            .map(|(x, _)| x)
            .cloned()
            .collect::<std::collections::BTreeSet<_>>();

        assert!(
            endowed_accounts.len() == self.balances.len(),
            "duplicate balances in genesis."
        );

        for &(ref who, free) in self.balances.iter() {
            assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() })
                .is_ok());
        }
    }
}

In this case, the specific accounts you want to use are not hardcoded into your pallet, but are inserted from the genesis configuration like:

/// Configure initial storage state for FRAME modules.
fn testnet_genesis(
    wasm_binary: &[u8],
    initial_authorities: Vec<(AuraId, GrandpaId)>,
    root_key: AccountId,
    endowed_accounts: Vec<AccountId>,
    _enable_println: bool,
) -> GenesisConfig {
    GenesisConfig {
        system: SystemConfig {
            // Add Wasm runtime to storage.
            code: wasm_binary.to_vec(),
        },
        balances: BalancesConfig {
            // Configure endowed accounts with initial balance of 1 << 60.
            balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(),
        },
        aura: AuraConfig {
            authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(),
        },
        grandpa: GrandpaConfig {
            authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(),
        },
        sudo: SudoConfig {
            // Assign network admin rights.
            key: Some(root_key),
        },
        transaction_payment: Default::default(),
    }
}
0
4

Did you declare AccountId as AccountId32?

I assume you are writing a unit test and have some sort of mock runtime where you have the following somewhere:

impl frame_system::Config for Runtime {
    ...
    type AccountId = AccountId32;
    ...
}

If you have this, but use a different AccountId type, you will need to use this one in your test.

However, as I'm not aware of the entire context you are writing this test in, you could also do something like this:

let account_collection = vec![sp_keyring::sr25519::Keyring::Alice.to_account_id()].encode();

AccountsCollection::<T>::put(&Vec::<_>::decode(&mut &account_collection[..]);

However, I still assume that you haven't declared AccountId32 as the value of AccountId in your mock runtime.

1
  • When you say "but use a different AccountId type, you will need to use this one in your test", I assume you mean that if they use an AccountId value that isn't AccountId32 in their implementation then they should use that other value in their test? Is that assumption correct? Mar 27, 2022 at 19:49

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.