ink icon indicating copy to clipboard operation
ink copied to clipboard

How to calculate child storage key for hashmap storage item? (contracts_getStorage RPC)

Open ychen-1202 opened this issue 4 years ago • 1 comments

I was trying to get contract storage by api.rpc.contracts.getStorage(). I could get storage with u32 data type storage item by cell key generate in metadata.json. But cannot use the same way to get hashmap type storage item. Wondering what's the correct way get contract storage of hashmap data? Suppose I insert a key-value like "1" => 2 in test_map & "5Dk1M31RWz5D8okdxtLMcUrMmKKPMGjEfwcQvzVqjNgDzwME" => 3 in registered_role_type.

Here's my ink storage:

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
pub mod dataStorage {
    use scale::{Encode, Decode, WrapperTypeEncode};
    use ink_storage::{
        collections::HashMap,
        Pack,
        traits::{PackedLayout, SpreadLayout},
    };
    use derive_deref::Deref;
    use ink_prelude::{
        collections::BTreeMap,
        string::String,
        vec::Vec,
        format
    };

    #[ink(storage)]
    pub struct DataStorage {
        /// Stores a single `bool` value on the storage.
        registered_role_type: HashMap<AccountId, u32>,
        test_map: HashMap<String, u32>,
    }

    impl DataStorage {
        #[ink(constructor)]
        pub fn new() -> Self {
            Self { 
                registered_role_type: Default::default(),
                test_map: Default::default(),
            }
        }

        /// Set role type
        #[ink(message)]
        pub fn setRoleType(&mut self, account: AccountId, role_type: u32) {
            self.registered_role_type.insert(account, role_type);
        }

        /// Get role type
        #[ink(message)]
        pub fn getRoleType(&mut self, account: AccountId) -> (AccountId, u32) {
            let role_type = self.registered_role_type.get(&account).unwrap_or(&0);
            (account, *role_type)
        }

        /// Set test map 
        #[ink(message)]
        pub fn setTestMap(&mut self, key: String, value: u32) {
            self.test_map.insert(key, value);
        }

        /// Get test map 
        #[ink(message)]
        pub fn getTestMap(&mut self, key: String) -> (String, u32) {
            let value = self.test_map.get(&key).unwrap_or(&0);
            (key, *value)
        }
    }
}

And the metadata for hashmap registered_role_type & test_map look like this:

{
          "layout": {
            "struct": {
              "fields": [
                {
                  "layout": {
                    "struct": {
                      "fields": [
                        {
                          "layout": {
                            "cell": {
                              "key": "0x0100000000000000000000000000000000000000000000000000000000000000",
                              "ty": 2
                            }
                          },
                          "name": "header"
                        },
                        {
                          "layout": {
                            "struct": {
                              "fields": [
                                {
                                  "layout": {
                                    "cell": {
                                      "key": "0x0200000000000000000000000000000000000000000000000000000000000000",
                                      "ty": 3
                                    }
                                  },
                                  "name": "len"
                                },
                                {
                                  "layout": {
                                    "array": {
                                      "cellsPerElem": 1,
                                      "layout": {
                                        "cell": {
                                          "key": "0x0200000001000000000000000000000000000000000000000000000000000000",
                                          "ty": 4
                                        }
                                      },
                                      "len": 4294967295,
                                      "offset": "0x0300000000000000000000000000000000000000000000000000000000000000"
                                    }
                                  },
                                  "name": "elems"
                                }
                              ]
                            }
                          },
                          "name": "entries"
                        }
                      ]
                    }
                  },
                  "name": "keys"
                },
                {
                  "layout": {
                    "hash": {
                      "layout": {
                        "cell": {
                          "key": "0x0300000001000000000000000000000000000000000000000000000000000000",
                          "ty": 9
                        }
                      },
                      "offset": "0x0200000001000000000000000000000000000000000000000000000000000000",
                      "strategy": {
                        "hasher": "Blake2x256",
                        "postfix": "",
                        "prefix": "0x696e6b20686173686d6170"
                      }
                    }
                  },
                  "name": "values"
                }
              ]
            }
          },
          "name": "registered_role_type"
        },
        {
          "layout": {
            "struct": {
              "fields": [
                {
                  "layout": {
                    "struct": {
                      "fields": [
                        {
                          "layout": {
                            "cell": {
                              "key": "0x0500000002000000000000000000000000000000000000000000000000000000",
                              "ty": 2
                            }
                          },
                          "name": "header"
                        },
                        {
                          "layout": {
                            "struct": {
                              "fields": [
                                {
                                  "layout": {
                                    "cell": {
                                      "key": "0x0600000002000000000000000000000000000000000000000000000000000000",
                                      "ty": 3
                                    }
                                  },
                                  "name": "len"
                                },
                                {
                                  "layout": {
                                    "array": {
                                      "cellsPerElem": 1,
                                      "layout": {
                                        "cell": {
                                          "key": "0x0600000003000000000000000000000000000000000000000000000000000000",
                                          "ty": 12
                                        }
                                      },
                                      "len": 4294967295,
                                      "offset": "0x0700000002000000000000000000000000000000000000000000000000000000"
                                    }
                                  },
                                  "name": "elems"
                                }
                              ]
                            }
                          },
                          "name": "entries"
                        }
                      ]
                    }
                  },
                  "name": "keys"
                },
                {
                  "layout": {
                    "hash": {
                      "layout": {
                        "cell": {
                          "key": "0x0700000003000000000000000000000000000000000000000000000000000000",
                          "ty": 9
                        }
                      },
                      "offset": "0x0600000003000000000000000000000000000000000000000000000000000000",
                      "strategy": {
                        "hasher": "Blake2x256",
                        "postfix": "",
                        "prefix": "0x696e6b20686173686d6170"
                      }
                    }
                  },
                  "name": "values"
                }
              ]
            }
          },
          "name": "test_map"
        }

I also tried to use rpc.childstate.getKeys() to get child keys, but don't know how to calculate childKey: PrefixedStorageKey and prefix: StorageKey. Is childKey: PrefixedStorageKey equal to child trieId in ContractInfo?

ychen-1202 avatar Apr 29 '21 13:04 ychen-1202

Folks are just moving this issue around to different repos. No one is actually answering the question :-1:

devanshu0987 avatar Oct 22 '21 03:10 devanshu0987

Hi @ychen-1202

For the most recent version ink! v4.0, storage key calculation for mappings is explained in our docs.

In a nutshell, SCALE encode the mapping key to the base key of the mapping will result in the storage key of the mapping value.

Suppose you have the following contract:

#![cfg_attr(not(feature = "std"), no_std)]

#[ink::contract]
mod contract {
    use ink::storage::Mapping;

    #[derive(Default)]
    #[ink(storage)]
    pub struct Contract {
        roles: Mapping<AccountId, u32>,
    }

    impl Contract {
        #[ink(constructor)]
        pub fn new() -> Self {
            Default::default()
        }

        #[ink(message)]
        pub fn set(&mut self, role: u32) {
            self.roles.insert(self.env().caller(), &role);
        }

        #[ink(message)]
        pub fn get(&self) -> Option<u32> {
            self.roles.get(self.env().caller())
        }
    }
}

Which will produce the following storage layout:

"storage": {
    "root": {
      "layout": {
        "struct": {
          "fields": [
            {
              "layout": {
                "root": {
                  "layout": {
                    "leaf": {
                      "key": "0xb492e427",
                      "ty": 0
                    }
                  },
                  "root_key": "0xb492e427"
                }
              },
              "name": "roles"
            }
          ],
          "name": "Contract"
        }
      },
      "root_key": "0x00000000"
    }
  }

Now, let's suppose we are interested in what roles are set for the alice account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY.

  1. SCALE encoded base key of the mapping (0xb492e427) will be 0x27e492b4
  2. SCALE encode the AccountId, it will be 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d. Note that you'll need to convert the SS58 into a AccountId32 first.
  3. Concatenating those two will result in the key 0x27e492b4d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d which can then be used in the contractsApi runtime call API.
let account_id = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
let account: AccountId32 = Ss58Codec::from_string(account_id).unwrap();
println!("0x{}", hex::encode(&(0xb492e427u32, account).encode()));

xermicus avatar Feb 21 '23 20:02 xermicus

Thanks very much @xermicus

forgetso avatar Feb 22 '23 08:02 forgetso