3

Hey guys I'm seeing a weird thing with the following migration code:

pub struct SomeMigration;
impl OnRuntimeUpgrade for SomeMigration {
    fn on_runtime_upgrade() -> Weight {
        let pallet_prefix: &[u8] = b"SomeName";
        let storage_item_prefix: &[u8] = b"TestMe";

        let value = match take_storage_value::<u32>(pallet_prefix, storage_item_prefix, &[]) {
            Some(value) => value,
            None => {
                return 0;
            }
        };
        let new_value: u128 = value as u128;
        put_storage_value::<u128>(pallet_prefix, storage_item_prefix, &[], new_value);

        1
    }

    #[cfg(feature = "try-runtime")]
    fn pre_upgrade() -> Result<(), &'static str> {
        let pallet_prefix: &[u8] = b"SomeName";
        let storage_item_prefix: &[u8] = b"TestMe";
        assert!(get_storage_value::<u32>(pallet_prefix, storage_item_prefix, &[]).is_none());
        put_storage_value(pallet_prefix, storage_item_prefix, &[], 10u32);
        assert!(get_storage_value::<u32>(pallet_prefix, storage_item_prefix, &[]).is_some());

        Ok(())
    }

    #[cfg(feature = "try-runtime")]
    fn post_upgrade() -> Result<(), &'static str> {
        let pallet_prefix: &[u8] = b"SomeName";
        let storage_item_prefix: &[u8] = b"TestMe";
        assert!(get_storage_value::<u32>(pallet_prefix, storage_item_prefix, &[]).is_none());

        Ok(())
    }
}

What's happening here is is the following

  1. pre-upgrade: Check that no item exists for u32 storage value TestMe
  2. pre-upgrade: Write a u32 storage value TestMe
  3. pre-upgrade: Check that the u32 storage value is_some()
  4. on-runtime-upgrade: take_storage_value of the u32 TestMe, expecting it to be drained
  5. on-runtime-upgrade: write the same value back but as u128
  6. on-post-upgrade: check that the old u32 value is_none()

The problem is that when I use the try-runtime tool the assert on step 6 fails, as if the value was not drained in step 4. But if I actually perform this runtime upgrade on a local network I can see that the old storage key of the u32 storage value is empty after the upgrade and only the new u128 storage value exists.

This seems like some issue with the try-runtime tool, but I may be using it wrong.

Thanks for the attention.

EDIT: storage_alias minimal example:

pub mod old {
    #[storage_alias]
    pub type SomeName = StorageValue<PalletName, u32, ValueQuery>;
}

#[storage_alias]
pub type SomeName = StorageValue<PalletName, u128, ValueQuery>;

pub struct SomeMigration;
impl OnRuntimeUpgrade for SomeMigration {
    fn on_runtime_upgrade() -> Weight {
        let value = old::SomeName::take();
        SomeName::set(value as u128);

        1
    }

    #[cfg(feature = "try-runtime")]
    fn pre_upgrade() -> Result<(), &'static str> {
        old::SomeName::set(10u32);

        Ok(())
    }

    #[cfg(feature = "try-runtime")]
    fn post_upgrade() -> Result<(), &'static str> {
        assert_eq!(old::SomeName::get(), 10u32);

        Ok(())
    }
}
3
  • Do I see it correctly that you write a u128 to storage and then decode it as u32? This should work. You should probably use a storage_alias here to make it easier to read. Oct 6, 2022 at 18:00
  • I see. I'm just trying to assert that the old u32 value is purged. With storage_alias the same thing happens. I use take() on the old::SomeName in on-runtime-upgrade and then the value remains in post-upgrade when I use old::SomeName::get() Oct 7, 2022 at 12:37
  • 1
    I've edited the question with the minimal example with storage_alias ``` Oct 7, 2022 at 12:49

1 Answer 1

5

There is no magic involved here. The key of the storage location is

Twox128(Prefix::pallet_prefix()) ++ Twox128(Prefix::STORAGE_PREFIX)

(explained here)

In your case the old storage hash and the new one are identical. You will therefore still see a value at that location. This is perfectly fine.
In some migrations you can check that the old value does not decode anymore, but in your case it can decode since u32 decodes from a u128. The SCALE coded is an acronym for "simple concatenated aggregate little-endian" and therefore the little-endianness here makes it possible.
You can double-check that with a simple test:

let short = 0xffffffffu32.encode();
let long = 0xffffffffu128.encode();
assert_eq!(short, long[..4]);

One example of checking whether old values in a map are still decodable would be in the scheduler migration.

1
  • Yes of-course, makes sense now, thanks for clarifying. Oct 10, 2022 at 18:15

Your Answer

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

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