How to calculate child storage key for hashmap storage item? (contracts_getStorage RPC)
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?
Folks are just moving this issue around to different repos. No one is actually answering the question :-1:
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.
SCALEencoded base key of the mapping (0xb492e427) will be0x27e492b4SCALEencode theAccountId, it will be0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d. Note that you'll need to convert the SS58 into aAccountId32first.- Concatenating those two will result in the key
0x27e492b4d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dwhich can then be used in thecontractsApiruntime call API.
let account_id = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
let account: AccountId32 = Ss58Codec::from_string(account_id).unwrap();
println!("0x{}", hex::encode(&(0xb492e427u32, account).encode()));
Thanks very much @xermicus