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:
We observe that the storage layout is represented as a tree, where tangible storage values end up inside a leaf
. Because of Packed
encoding, leafs can share the same storage key, and in order to reach them you'd need to fetch and decode the whole storage cell under this key.
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).
Storage key calculation for ink! Mapping
values
Mapping
valuesBase 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
.
Accessing storage items with the contractsApi
runtime call API
contractsApi
runtime call APIThere are two ways to query for storage fields of smart contracts from outside a contract. Both methods are accessible via the polkadot-js
web UI.
The straight forward way to query a contracts storage is via a runtime API
call, using the contractsApi
endpoint provided by the contracts pallet. The endpoint provides a getStorage
method, which just expects a contract address and a storage key as arguments.
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.
Accessing storage items with the childState
RPC call API
childState
RPC call APIUnder the hood, each contract gets its own child trie, where its storage items are actually stored.
Additionally, the contracts pallet uses the Blake2 128 Concat
Transparent hashing algorithm
to calculate storage keys for any stored item inside the child trie. You'll need to account for that as well.
With that in mind, to directly access storage items of any on-chain contract using a childState RPC call
, you'll need the following:
The child trie ID of the contract, represented as a
PrefixedStorageKey
The hashed storage key of the storage field
Finding the contracts child trie ID
The child trie ID is the Blake2_256
hash of the contracts instantiation nonce concatenated to it's AccountId
. You can find it in polkadot-js chainstate query interface
, where you need to execute the contracts_contractInfoOf
state query.
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.
Calculate the PrefixedStorageKey
from the child trie ID
PrefixedStorageKey
from the child trie IDA PrefixedStorageKey
based on the child trie ID can be constructed using the ChildInfo
primitive as follows:
Calculate the storage key using transparent hashing
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:
A full example
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