Is there a standard definition of when storage migrations are required?
I believe the most concise answer to this is to consider your blockchain two separate parts that need to work together:
- The logic of your chain (runtime)
- The state of your chain (storage)
If at any point these two parts become incompatible with one another, your chain is basically bricked.
Thus, the question you need to ask yourself when upgrading the logic of your chain, is what expectations it has on the underlying storage available.
For example, adding a new callable function (extrinsic) does not affect the behavior or layout of your storage, so no such storage migration is needed. Also, removing callable functions, or even entire modules has no impact. Adding new storage items, which will start empty also have no impact. There are a ton of things which can change in your runtime logic, which have no impact on storage, and thus needs no migration.
One reason you might need to migrate storage is a change in the serialization of the storage, which can come from refactoring the layout of structures.
For example, imagine something as simple as changing:
struct MyStruct<T> {
id: u32,
value: T::Balance,
}
Into:
struct MyStruct<T> {
value: T::Balance,
id: u32,
}
Nothing about the content or meaning of the struct has changed. In fact, the total encoded bytes of the object has stayed exactly the same, but the order of the items in the struct has flipped. This means, when the struct was originally put into storage, the first bits in the serialized form of the struct was the id
, but after the logic change, it will try to parse those same bits as the value
, and this will lead to problems. This actually won't brick your chain, as the encoded bytes will still fit perfectly, but you will end up totally misrepresenting the intended data.
So ultimately the question you need to ask yourself is:
Did an upgrade to the logic change the assumptions about the underlying storage?
If yes, then you need a storage migration.