When updating types in a runtime migration, you must read the "old" storage using the old storage type.
Imagine you have an item originally stored as:
struct Item {
id: u32,
field1: Field,
}
Then imagine you update the item to be:
struct Item {
id: u32,
field1: Field,
field2: Field,
}
If you were to try to read this storage item after updating the struct Item
, you will get the "corrupt state" error because on your blockchain, there is no field2
, and thus, when trying to read from state to create the new Item
, it will fail.
Instead, a common pattern for migrations is to write a special type which acts as a way to read the old storage, and then re-write the storage with the new updated struct.
That pattern looks like this:
mod old {
pub struct OldItem {
id: u32,
field1: Field,
}
#[frame_support::storage_alias]
pub type ItemMap<T> = StorageMap<
crate::Pallet<T>,
Twox64Concat,
u32,
OldItem<T>,
>;
}
fn migration() {
// Note here we are reading from `old`, which has our old type.
for (_key, old_item) in old::ListNodes::<T, I>::iter() {
// Note here we are using the `crate::Item`, which has the updated object.
let new_item = crate::Item {
id: old_item.id,
field1: old_item.field1,
// Here we just duplicate field1, but you may have your own logic...
field2: old_item.field1,
};
// Note that we write back to `crate::ItemMap` which uses the new type.
crate::ItemMap::<T>::insert(old_item.id, new_item);
}
}
There are plenty of examples in Substrate, but here is one:
https://github.com/paritytech/substrate/blob/master/frame/bags-list/src/migrations.rs
asset_id
in your script?let roc_id: <T as pallet_assets::Config>::AssetId = 0u32.into();