From 4de8d5cef9cfb6e41c79be4ee63e6017a445e1b2 Mon Sep 17 00:00:00 2001
From: thiolliere <gui.thiolliere@gmail.com>
Date: Mon, 13 Jan 2020 12:14:44 +0100
Subject: [PATCH] Add translate function for storage prefixed map. (#4555)

* translace values for prefixed storages

* improve doc

* new impl

* update test
---
 substrate/frame/support/src/storage/mod.rs | 72 ++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/substrate/frame/support/src/storage/mod.rs b/substrate/frame/support/src/storage/mod.rs
index 53e7ceb45df..0908adae3b6 100644
--- a/substrate/frame/support/src/storage/mod.rs
+++ b/substrate/frame/support/src/storage/mod.rs
@@ -403,6 +403,7 @@ pub trait StoragePrefixedMap<Value: FullCodec> {
 	/// Storage prefix. Used for generating final key.
 	fn storage_prefix() -> &'static [u8];
 
+	/// Final full prefix that prefixes all keys.
 	fn final_prefix() -> [u8; 32] {
 		let mut final_key = [0u8; 32];
 		final_key[0..16].copy_from_slice(&Twox128::hash(Self::module_prefix()));
@@ -410,10 +411,12 @@ pub trait StoragePrefixedMap<Value: FullCodec> {
 		final_key
 	}
 
+	/// Remove all value of the storage.
 	fn remove_all() {
 		sp_io::storage::clear_prefix(&Self::final_prefix())
 	}
 
+	/// Iter over all value of the storage.
 	fn iter() -> PrefixIterator<Value> {
 		let prefix = Self::final_prefix();
 		PrefixIterator {
@@ -422,6 +425,51 @@ pub trait StoragePrefixedMap<Value: FullCodec> {
 			phantom_data: Default::default(),
 		}
 	}
+
+	/// Translate the values from some previous `OldValue` to the current type.
+	///
+	/// `TV` translates values.
+	///
+	/// Returns `Err` if the map could not be interpreted as the old type, and Ok if it could.
+	/// The `Err` contains the number of value that couldn't be interpreted, those value are
+	/// removed from the map.
+	///
+	/// # Warning
+	///
+	/// This function must be used with care, before being updated the storage still contains the
+	/// old type, thus other calls (such as `get`) will fail at decoding it.
+	///
+	/// # Usage
+	///
+	/// This would typically be called inside the module implementation of on_initialize, while
+	/// ensuring **no usage of this storage are made before the call to `on_initialize`**. (More
+	/// precisely prior initialized modules doesn't make use of this storage).
+	fn translate_values<OldValue, TV>(translate_val: TV) -> Result<(), u32>
+		where OldValue: Decode, TV: Fn(OldValue) -> Value
+	{
+		let prefix = Self::final_prefix();
+		let mut previous_key = prefix.to_vec();
+		let mut errors = 0;
+		while let Some(next_key) = sp_io::storage::next_key(&previous_key)
+			.filter(|n| n.starts_with(&prefix[..]))
+		{
+			if let Some(value) = unhashed::get(&next_key) {
+				unhashed::put(&next_key[..], &translate_val(value));
+			} else {
+				// We failed to read the value. Remove the key and increment errors.
+				unhashed::kill(&next_key[..]);
+				errors += 1;
+			}
+
+			previous_key = next_key;
+		}
+
+		if errors == 0 {
+			Ok(())
+		} else {
+			Err(errors)
+		}
+	}
 }
 
 #[cfg(test)]
@@ -463,6 +511,7 @@ mod test {
 			let k = [twox_128(b"MyModule"), twox_128(b"MyStorage")].concat();
 			assert_eq!(MyStorage::final_prefix().to_vec(), k);
 
+			// test iteration
 			assert_eq!(MyStorage::iter().collect::<Vec<_>>(), vec![]);
 
 			unhashed::put(&[&k[..], &vec![1][..]].concat(), &1u64);
@@ -472,9 +521,32 @@ mod test {
 
 			assert_eq!(MyStorage::iter().collect::<Vec<_>>(), vec![1, 2, 3, 4]);
 
+			// test removal
 			MyStorage::remove_all();
+			assert_eq!(MyStorage::iter().collect::<Vec<_>>(), vec![]);
+
+			// test migration
+			unhashed::put(&[&k[..], &vec![1][..]].concat(), &1u32);
+			unhashed::put(&[&k[..], &vec![8][..]].concat(), &2u32);
 
 			assert_eq!(MyStorage::iter().collect::<Vec<_>>(), vec![]);
+			MyStorage::translate_values(|v: u32| v as u64).unwrap();
+			assert_eq!(MyStorage::iter().collect::<Vec<_>>(), vec![1, 2]);
+			MyStorage::remove_all();
+
+			// test migration 2
+			unhashed::put(&[&k[..], &vec![1][..]].concat(), &1u128);
+			unhashed::put(&[&k[..], &vec![1, 1][..]].concat(), &2u64);
+			unhashed::put(&[&k[..], &vec![8][..]].concat(), &3u128);
+			unhashed::put(&[&k[..], &vec![10][..]].concat(), &4u32);
+
+			// (contains some value that successfully decoded to u64)
+			assert_eq!(MyStorage::iter().collect::<Vec<_>>(), vec![1, 2, 3]);
+			assert_eq!(MyStorage::translate_values(|v: u128| v as u64), Err(2));
+			assert_eq!(MyStorage::iter().collect::<Vec<_>>(), vec![1, 3]);
+			MyStorage::remove_all();
+
+			// test that other values are not modified.
 			assert_eq!(unhashed::get(&key_before[..]), Some(32u64));
 			assert_eq!(unhashed::get(&key_after[..]), Some(33u64));
 		});
-- 
GitLab