Search code examples
rustsubstratepolkadot

How to have immutable key-value map in Substrate?


I am writing a key: value StorageMap in substrate. I want to make it immutable such that a key is written if non-existent but if it exists:

i) if the value is the same as stored, alright ii) invalidate the transaction.

I have written the following runtime code :

use support::{decl_module, decl_storage, dispatch::Result, StorageMap};
use system::ensure_signed;

pub trait Trait: balances::Trait {}

decl_storage! {
    trait Store for Module<T: Trait> as KittyStorage {
        Value: map u64 => T::AccountId;
    }
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {

        fn set_value(origin, value: u64) -> Result {
            let sender = ensure_signed(origin)?;

            <Value<T>>::insert(value, sender);

            Ok(())
        }
    }
}

The official tutorial talks about mutating the key in the following manner:

/// Mutate the value under a key.
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: Storage>(key: &K, f: F, storage: &S) -> R;

So, how do I make my key:value immutable? Should I write my own StorageMap? If yes, where should I place that code?

Note: I am new to both substrate and rust.


Solution

  • I want to make it immutable such that a key is written if non-existent but if it exists:

    i) if the value is the same as stored, alright ii) invalidate the transaction.

    You can use the exists/contains_key api on the storage item, and you should probably be even more explicit by using an Option.

    So taking the code you have written, you would modify it like so:

    use support::{decl_module, decl_storage, dispatch::Result, ensure, StorageMap};
    use system::ensure_signed;
    
    pub trait Trait: balances::Trait {}
    
    decl_storage! {
        trait Store for Module<T: Trait> as KittyStorage {
            Value: map u64 => Option<T::AccountId>;
        }
    }
    
    decl_module! {
        pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    
            fn set_value(origin, value: u64) -> Result {
                let sender = ensure_signed(origin)?;
    
                ensure!(!<Value<T>>::contains_key(value), "key already exists");
                <Value<T>>::insert(value, sender);
    
                Ok(())
            }
        }
    }
    

    Since you are using an Option here, you can also read the value and check if it is Some(value) or None and then error or continue as a result.

    What you cannot do is truly make the value immutable in storage such that all code would know not to change the value. You would need to write logic to check ahead of time the value is there so that you don't change it.