Metadata Format
The storage layout of a contract is reflected inside the metadata. It allows third-party tooling to work with contract storage and can also help to better understand the storage layout of any given contract.
Given a contract with the following storage:
The storage will be reflected inside the metadata as like follows:
A root_key
is meant to either be used to directly access a Packed
storage field or to serve as the base key for calculating the actual keys needed to access values in non-Packed
fields (such as Mapping
s).
Base storage keys are always 4 bytes in size. However, the storage API of the contracts pallet supports keys of arbitrary length. In order to reach a mapping value, the storage key of said value is calculated at runtime.
The formula to calculate the base storage key S
used to access a mapping value under the key K
for a mapping with base key B
can be expressed as follows:
Where the base key B
is the root_key
(of type u32
) found in the contract metadata.
In words, SCALE encode the base (root) key of the mapping and concatenate it with the SCALE encoded key of the mapped value to obtain the actual storage key used to access the mapped value.
Given the following contract storage, which maps accounts to a balance:
Now let's suppose we are interested in finding the balance for the account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
. The storage key is calculated as follows:
SCALE encode the base key of the mapping (
0x12345678u32
), resulting in0x78563412
SCALE encode the
AccountId
, which will be0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
. Note that you'll need to convert the SS58 into aAccountId32
first.Concatenating those two will result in the key
0x78563412d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
.
For example, to access the root storage struct under the key 0x00000000
of a contract, just specify the contract's address and the storage key 0x00000000
as-is. The API call will return the scale-encoded root storage struct of the contract.
The hashed storage key of the storage field
It can also be calculate manually according to the following code snippet. The instantiation note of the contract must be still be known. You can get it using the contracts_nonce
chain state query in polkadot-js UI.
A PrefixedStorageKey
based on the child trie ID can be constructed using the ChildInfo
primitive as follows:
Finally, we calculate the hashed storage key of the storage item we are wanting to access. The algorithm is simple: Blake2_128
hash the storage key and then concatenate the unhashed key to the hash. Given you want to access the storage item under the 0x00000000
, it will look like this in code:
Let's recap the last few paragraphs into a full example. Given:
A contract at address
5DjcHxSfjAgCTSF9mp6wQBJWBgj9h8uh57c7TNx1mL5hdQp4
With instantiation nonce of
1
The root storage struct is to be found at base key
0x00000000
The following Rust program demonstrates how to calculate the PrefixedStorageKey
of the contracts child trie, as well as the hashed key for the storage struct, which can then be used with the chilstate
RPC endpoint function getStorage
in polkadot-js to receive the root storage struct of the contract:
Last updated
Was this helpful?