From 521e9fa5131364a95882627aec6f4274e32bdb66 Mon Sep 17 00:00:00 2001 From: Shunji Zhan Date: Fri, 29 Nov 2024 14:27:27 +0800 Subject: [PATCH] use query storage helper (#1051) * use query storage helper * test * fix * fix * update * add tests * updatre * skip test --- .../src/__tests__/BlockCache.test.ts | 2 +- .../src/__tests__/MaxSizeSet.test.ts | 5 +- .../src/__tests__/decimals.test.ts | 1 + .../__tests__/dispatch-error-match.test.ts | 3 +- .../src/__tests__/evm-rpc-provider.test.ts | 5 +- .../__tests__/getTransactionReceipt.test.ts | 7 +- .../src/__tests__/json-rpc-provider.test.ts | 6 +- .../eth-providers/src/__tests__/logs.test.ts | 1 + .../src/__tests__/parseBlock.test.ts | 3 +- .../src/__tests__/safemode.test.ts | 6 +- .../eth-providers/src/__tests__/tx.test.ts | 8 +- .../eth-providers/src/__tests__/utils.test.ts | 74 ++++++++++++++++++- .../src/__tests__/utils/evmAccounts.ts | 2 +- .../src/__tests__/utils/index.ts | 2 + .../src/__tests__/utils/testUtils.ts | 5 ++ packages/eth-providers/src/base-provider.ts | 56 +++++++++----- .../eth-providers/src/utils/parseBlock.ts | 6 +- .../eth-providers/src/utils/queryStoarge.ts | 53 +++++++++++++ 18 files changed, 198 insertions(+), 47 deletions(-) create mode 100644 packages/eth-providers/src/__tests__/utils/index.ts create mode 100644 packages/eth-providers/src/utils/queryStoarge.ts diff --git a/packages/eth-providers/src/__tests__/BlockCache.test.ts b/packages/eth-providers/src/__tests__/BlockCache.test.ts index 0370fafd7..0053e2f39 100644 --- a/packages/eth-providers/src/__tests__/BlockCache.test.ts +++ b/packages/eth-providers/src/__tests__/BlockCache.test.ts @@ -2,7 +2,7 @@ import { TransactionReceipt } from '@ethersproject/abstract-provider'; import { BlockCache } from '../utils/BlockCache'; import { describe, expect, it } from 'vitest'; -import { mockChain } from './utils/testUtils'; +import { mockChain } from './utils'; const sortReceipt = (r1: TransactionReceipt, r2: TransactionReceipt) => { if (r1.blockNumber !== r2.blockNumber) { diff --git a/packages/eth-providers/src/__tests__/MaxSizeSet.test.ts b/packages/eth-providers/src/__tests__/MaxSizeSet.test.ts index b7c2d2c21..c10529421 100644 --- a/packages/eth-providers/src/__tests__/MaxSizeSet.test.ts +++ b/packages/eth-providers/src/__tests__/MaxSizeSet.test.ts @@ -1,6 +1,7 @@ -import { MaxSizeSet } from '../utils/MaxSizeSet'; import { describe, expect, it } from 'vitest'; -import { mockChain } from './utils/testUtils'; + +import { MaxSizeSet } from '../utils/MaxSizeSet'; +import { mockChain } from './utils'; describe('MaxSizeSet', () => { const MAX_CACHED_BLOCK = 5; diff --git a/packages/eth-providers/src/__tests__/decimals.test.ts b/packages/eth-providers/src/__tests__/decimals.test.ts index 28098dcd0..13fc4ad79 100644 --- a/packages/eth-providers/src/__tests__/decimals.test.ts +++ b/packages/eth-providers/src/__tests__/decimals.test.ts @@ -1,6 +1,7 @@ import { BigNumber } from '@ethersproject/bignumber'; import { describe, expect, it } from 'vitest'; import { hexValue } from '@ethersproject/bytes'; + import { nativeToEthDecimal } from '../utils'; describe('decimals', () => { diff --git a/packages/eth-providers/src/__tests__/dispatch-error-match.test.ts b/packages/eth-providers/src/__tests__/dispatch-error-match.test.ts index 1aca1bd91..a7be841fb 100644 --- a/packages/eth-providers/src/__tests__/dispatch-error-match.test.ts +++ b/packages/eth-providers/src/__tests__/dispatch-error-match.test.ts @@ -1,6 +1,7 @@ -import { ERROR_PATTERN } from '../consts'; import { describe, expect, it } from 'vitest'; +import { ERROR_PATTERN } from '../consts'; + describe('filterLog', () => { const err0 = 'Error: -32603: execution fatal: Module(ModuleError { index: 180, error: [11, 0, 0, 0], message: None })'; diff --git a/packages/eth-providers/src/__tests__/evm-rpc-provider.test.ts b/packages/eth-providers/src/__tests__/evm-rpc-provider.test.ts index abc655fec..0da72c8c4 100644 --- a/packages/eth-providers/src/__tests__/evm-rpc-provider.test.ts +++ b/packages/eth-providers/src/__tests__/evm-rpc-provider.test.ts @@ -1,8 +1,9 @@ -import { EvmRpcProvider } from '../rpc-provider'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { runWithTiming, sleep } from '../utils'; import dotenv from 'dotenv'; +import { EvmRpcProvider } from '../rpc-provider'; +import { sleep } from '../utils'; + dotenv.config(); const ACALA_NODE_URL = 'wss://acala-rpc.dwellir.com'; diff --git a/packages/eth-providers/src/__tests__/getTransactionReceipt.test.ts b/packages/eth-providers/src/__tests__/getTransactionReceipt.test.ts index 1514eff38..b3a334959 100644 --- a/packages/eth-providers/src/__tests__/getTransactionReceipt.test.ts +++ b/packages/eth-providers/src/__tests__/getTransactionReceipt.test.ts @@ -1,16 +1,15 @@ import { Contract } from '@ethersproject/contracts'; import { Wallet } from '@ethersproject/wallet'; import { afterAll, describe, expect, it } from 'vitest'; +import { parseUnits } from 'ethers/lib/utils'; import ACAABI from '@acala-network/contracts/build/contracts/Token.json'; import ADDRESS from '@acala-network/contracts/utils/AcalaAddress'; import { EvmRpcProvider } from '../rpc-provider'; -import { parseUnits } from 'ethers/lib/utils'; -import evmAccounts from './utils/evmAccounts'; +import { evmAccounts, nodeUrl } from './utils'; describe('TransactionReceipt', async () => { - const endpoint = process.env.ENDPOINT_URL || 'ws://127.0.0.1:9944'; - const provider = EvmRpcProvider.from(endpoint); + const provider = EvmRpcProvider.from(nodeUrl); await provider.isReady(); afterAll(async () => { diff --git a/packages/eth-providers/src/__tests__/json-rpc-provider.test.ts b/packages/eth-providers/src/__tests__/json-rpc-provider.test.ts index 1785e0fb0..3300be733 100644 --- a/packages/eth-providers/src/__tests__/json-rpc-provider.test.ts +++ b/packages/eth-providers/src/__tests__/json-rpc-provider.test.ts @@ -4,11 +4,9 @@ import { describe, expect, it } from 'vitest'; import { hexZeroPad, parseEther } from 'ethers/lib/utils'; import { AcalaJsonRpcProvider } from '../json-rpc-provider'; +import { ethRpc, evmAccounts } from './utils'; import echoJson from './abis/Echo.json'; import erc20Json from './abis/IERC20.json'; -import evmAccounts from './utils/evmAccounts'; - -const localEthRpc = process.env.ETH_RPC || 'http://localhost:8545'; describe('JsonRpcProvider', async () => { /* --------- karura --------- */ @@ -20,7 +18,7 @@ describe('JsonRpcProvider', async () => { /* --------- local --------- */ const testKey = evmAccounts[0].privateKey; // 0x75E480dB528101a381Ce68544611C169Ad7EB342 - const providerLocal = new AcalaJsonRpcProvider(localEthRpc); + const providerLocal = new AcalaJsonRpcProvider(ethRpc); const wallet = new Wallet(testKey, providerLocal); describe.concurrent('get chain data', () => { diff --git a/packages/eth-providers/src/__tests__/logs.test.ts b/packages/eth-providers/src/__tests__/logs.test.ts index 75e651f9c..23975b34e 100644 --- a/packages/eth-providers/src/__tests__/logs.test.ts +++ b/packages/eth-providers/src/__tests__/logs.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { filterLog } from '../utils'; describe('filterLog', () => { diff --git a/packages/eth-providers/src/__tests__/parseBlock.test.ts b/packages/eth-providers/src/__tests__/parseBlock.test.ts index 8f71c41d0..4ff7156d5 100644 --- a/packages/eth-providers/src/__tests__/parseBlock.test.ts +++ b/packages/eth-providers/src/__tests__/parseBlock.test.ts @@ -3,8 +3,7 @@ import { TransactionReceipt } from '@ethersproject/abstract-provider'; import { afterAll, beforeAll, describe, it } from 'vitest'; import { options } from '@acala-network/api'; -import { getAllReceiptsAtBlock } from '../utils/parseBlock'; -import { hexlifyRpcResult, sleep } from '../utils'; +import { getAllReceiptsAtBlock, hexlifyRpcResult } from '../utils'; interface FormatedReceipt { to?: string; diff --git a/packages/eth-providers/src/__tests__/safemode.test.ts b/packages/eth-providers/src/__tests__/safemode.test.ts index 493cf13c0..3e7380f90 100644 --- a/packages/eth-providers/src/__tests__/safemode.test.ts +++ b/packages/eth-providers/src/__tests__/safemode.test.ts @@ -1,11 +1,11 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { EvmRpcProvider } from '../rpc-provider'; +import { nodeUrl } from './utils'; import { sleep } from '../utils'; -const endpoint = process.env.ENDPOINT_URL || 'ws://127.0.0.1:9944'; -const safeProvider = EvmRpcProvider.from(endpoint, { safeMode: true }); -const provider = EvmRpcProvider.from(endpoint, { safeMode: false }); +const safeProvider = EvmRpcProvider.from(nodeUrl, { safeMode: true }); +const provider = EvmRpcProvider.from(nodeUrl, { safeMode: false }); const newBlock = async (finalize: boolean): Promise => { await provider.api.rpc.engine.createBlock(true /* create empty */, finalize); diff --git a/packages/eth-providers/src/__tests__/tx.test.ts b/packages/eth-providers/src/__tests__/tx.test.ts index c33b4d8e6..b2ed2df1d 100644 --- a/packages/eth-providers/src/__tests__/tx.test.ts +++ b/packages/eth-providers/src/__tests__/tx.test.ts @@ -10,13 +10,11 @@ import ACAABI from '@acala-network/contracts/build/contracts/Token.json'; import ADDRESS from '@acala-network/contracts/utils/MandalaAddress'; import { EvmRpcProvider } from '../rpc-provider'; -import { calcEthereumTransactionParams } from '../utils'; -import { computeDefaultSubstrateAddress } from '../utils/address'; -import evmAccounts from './utils/evmAccounts'; +import { calcEthereumTransactionParams, computeDefaultSubstrateAddress } from '../utils'; +import { evmAccounts, nodeUrl } from './utils'; describe('transaction tests', () => { - const endpoint = process.env.ENDPOINT_URL || 'ws://127.0.0.1:9944'; - const provider = EvmRpcProvider.from(endpoint); + const provider = EvmRpcProvider.from(nodeUrl); const account1 = evmAccounts[0]; const account2 = evmAccounts[1]; diff --git a/packages/eth-providers/src/__tests__/utils.test.ts b/packages/eth-providers/src/__tests__/utils.test.ts index b7b99ae88..6ef752762 100644 --- a/packages/eth-providers/src/__tests__/utils.test.ts +++ b/packages/eth-providers/src/__tests__/utils.test.ts @@ -1,6 +1,9 @@ -import { describe, expect, it, vi } from 'vitest'; +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { FrameSystemAccountInfo } from '@polkadot/types/lookup'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { hexValue } from '@ethersproject/bytes'; +import { ALICE_ADDR, nodeUrl } from './utils'; import { CacheInspect } from '../utils/BlockCache'; import { EthCallTimingResult, @@ -13,8 +16,11 @@ import { runWithTiming, sleep, } from '../utils'; +import { EvmRpcProvider } from '../rpc-provider'; import { HeadsInfo } from '../base-provider'; import { _Metadata } from '../utils/gqlTypes'; +import { queryStorage } from '../utils/queryStoarge'; + describe('utils', () => { it('connect chain', async () => { @@ -507,3 +513,69 @@ describe('eth call error handling', () => { }); }); }); + +describe('query storage', () => { + let api: ApiPromise; + + + beforeAll(async () => { + api = await ApiPromise.create({ provider: new WsProvider(nodeUrl) }); + }); + + afterAll(async () => { + await api.disconnect(); + }); + + it('timestamp.now', async () => { + const _testQueryStorage = async (blockHash: string) => { + const timestamp = await queryStorage( + api, + 'timestamp.now', + [], + blockHash + ); + + const timestampReal = await (await api.at(blockHash)).query.timestamp.now(); + + console.log(timestamp.toJSON(), timestampReal.toJSON()); + expect(timestamp.toJSON()).to.deep.eq(timestampReal.toJSON()); + }; + + const curBlockHash = (await api.rpc.chain.getBlockHash()).toString(); + const curBlockNum = (await api.rpc.chain.getHeader()).number.toNumber(); + const randBlock = curBlockNum - Math.floor(Math.random() * 1000); + console.log(curBlockNum, randBlock); + const randBlockHash = (await api.rpc.chain.getBlockHash(randBlock)).toString(); + + await _testQueryStorage(curBlockHash); + await _testQueryStorage(randBlockHash); + }); + + // FIXME: improve query storage helper to return consistent results as apiAt.query + it.skip('system.account', async () => { + const _testQueryStorage = async (blockHash: string) => { + const accountInfo = await queryStorage( + api, + 'system.account', + [ALICE_ADDR], + blockHash + ); + + const accountInfoReal = await (await api.at(blockHash)).query.system.account(ALICE_ADDR); + + console.log(accountInfo.toJSON(), accountInfoReal.toJSON()); + expect(accountInfo.toJSON()).to.deep.eq(accountInfoReal.toJSON()); + }; + + const curBlockHash = (await api.rpc.chain.getBlockHash()).toString(); + const curBlockNum = (await api.rpc.chain.getHeader()).number.toNumber(); + const randBlock = curBlockNum - Math.floor(Math.random() * 1000); + const randBlockHash = (await api.rpc.chain.getBlockHash(randBlock)).toString(); + + console.log(curBlockNum, randBlock); + console.log(curBlockHash, randBlockHash); + + await _testQueryStorage(curBlockHash); + await _testQueryStorage(randBlockHash); // fails on 7332027 + }); +}); diff --git a/packages/eth-providers/src/__tests__/utils/evmAccounts.ts b/packages/eth-providers/src/__tests__/utils/evmAccounts.ts index ac97f9a1e..90f7eb24f 100644 --- a/packages/eth-providers/src/__tests__/utils/evmAccounts.ts +++ b/packages/eth-providers/src/__tests__/utils/evmAccounts.ts @@ -1,4 +1,4 @@ -export default [ +export const evmAccounts = [ { privateKey: '0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f', evmAddress: '0x75E480dB528101a381Ce68544611C169Ad7EB342', diff --git a/packages/eth-providers/src/__tests__/utils/index.ts b/packages/eth-providers/src/__tests__/utils/index.ts new file mode 100644 index 000000000..77442283d --- /dev/null +++ b/packages/eth-providers/src/__tests__/utils/index.ts @@ -0,0 +1,2 @@ +export * from './testUtils'; +export * from './evmAccounts'; diff --git a/packages/eth-providers/src/__tests__/utils/testUtils.ts b/packages/eth-providers/src/__tests__/utils/testUtils.ts index 589356289..43ef060e0 100644 --- a/packages/eth-providers/src/__tests__/utils/testUtils.ts +++ b/packages/eth-providers/src/__tests__/utils/testUtils.ts @@ -1,5 +1,10 @@ import { TransactionReceipt } from '@ethersproject/abstract-provider'; +export const nodeUrl = process.env.ENDPOINT_URL || 'ws://localhost:9944'; +export const ethRpc = process.env.ETH_RPC || 'http://localhost:8545'; + +export const ALICE_ADDR = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; + type MochBlock = { blockHash: string; blockNumber: number; diff --git a/packages/eth-providers/src/base-provider.ts b/packages/eth-providers/src/base-provider.ts index a09dd1ed1..bdedb2cb9 100644 --- a/packages/eth-providers/src/base-provider.ts +++ b/packages/eth-providers/src/base-provider.ts @@ -14,18 +14,19 @@ import { } from '@ethersproject/abstract-provider'; import { AcalaEvmTX, checkSignatureType, parseTransaction } from '@acala-network/eth-transactions'; import { AccessList, accessListify } from 'ethers/lib/utils'; +import { AccountId32, H160, H256, Header } from '@polkadot/types/interfaces'; import { ApiPromise } from '@polkadot/api'; import { AsyncAction } from 'rxjs/internal/scheduler/AsyncAction'; import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler'; import { BigNumber, BigNumberish, Wallet } from 'ethers'; import { Deferrable, defineReadOnly, resolveProperties } from '@ethersproject/properties'; import { Formatter } from '@ethersproject/providers'; -import { Header } from '@polkadot/types/interfaces'; +import { FrameSystemAccountInfo, ModuleEvmModuleAccountInfo } from '@polkadot/types/lookup'; import { ISubmittableResult } from '@polkadot/types/types'; import { Logger } from '@ethersproject/logger'; -import { ModuleEvmModuleAccountInfo } from '@polkadot/types/lookup'; import { Network } from '@ethersproject/networks'; import { Observable, ReplaySubject, Subscription, firstValueFrom, throwError } from 'rxjs'; +import { Option, u64 } from '@polkadot/types-codec'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { filter, first, timeout } from 'rxjs/operators'; import { getAddress } from '@ethersproject/address'; @@ -93,6 +94,7 @@ import { MaxSizeSet } from './utils/MaxSizeSet'; import { SubqlProvider } from './utils/subqlProvider'; import { _Metadata } from './utils/gqlTypes'; import { apiCache } from './utils/ApiAtCache'; +import { queryStorage } from './utils/queryStoarge'; export interface HeadsInfo { internalState: { @@ -596,8 +598,7 @@ export abstract class BaseProvider extends AbstractProvider { ); getTimestamp = async (blockHash: string): Promise => { - const apiAt = await apiCache.getApiAt(this.api, blockHash); - const timestamp = await apiAt.query.timestamp.now(); + const timestamp = await queryStorage(this.api, 'timestamp.now', [], blockHash); return timestamp.toNumber(); }; @@ -704,8 +705,12 @@ export abstract class BaseProvider extends AbstractProvider { const substrateAddress = await this.getSubstrateAddress(address, blockHash); - const apiAt = await apiCache.getApiAt(this.api, blockHash); - const accountInfo = await apiAt.query.system.account(substrateAddress); + const accountInfo = await queryStorage( + this.api, + 'system.account', + [substrateAddress], + blockHash + ); return nativeToEthDecimal(accountInfo.data.free.toBigInt()); }; @@ -746,9 +751,7 @@ export abstract class BaseProvider extends AbstractProvider { const contractInfo = evmAccountInfo?.contractInfo.unwrapOr(null); if (!contractInfo) { return '0x'; } - const apiAt = await apiCache.getApiAt(this.api, blockHash); - const code = await apiAt.query.evm.codes(contractInfo.codeHash); - + const code = await queryStorage(this.api, 'evm.codes', [contractInfo.codeHash], blockHash); return code.toHex(); }; @@ -844,8 +847,12 @@ export abstract class BaseProvider extends AbstractProvider { Promise.resolve(position).then(hexValue), ]); - const apiAt = await apiCache.getApiAt(this.api, blockHash); - const code = await apiAt.query.evm.accountStorages(address, hexZeroPad(resolvedPosition, 32)); + const code = await queryStorage( + this.api, + 'evm.accountStorages', + [address, hexZeroPad(resolvedPosition, 32)], + blockHash + ); return code.toHex(); }; @@ -948,7 +955,8 @@ export abstract class BaseProvider extends AbstractProvider { extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>, at?: string, ) => { - const apiAt = await apiCache.getApiAt(this.api, at ?? await this.bestBlockHash); + const blockHash = at ?? this.bestBlockHash; + const apiAt = await apiCache.getApiAt(this.api, blockHash); const u8a = extrinsic.toU8a(); const lenIncreaseAfterSignature = 100; // approximate length increase after signature @@ -1128,8 +1136,12 @@ export abstract class BaseProvider extends AbstractProvider { getSubstrateAddress = async (address: string, blockTag?: BlockTag): Promise => { const blockHash = await this._getBlockHash(blockTag); - const apiAt = await apiCache.getApiAt(this.api, blockHash); - const substrateAccount = await apiAt.query.evmAccounts.accounts(address); + const substrateAccount = await queryStorage>( + this.api, + 'evmAccounts.accounts', + [address], + blockHash + ); return substrateAccount.isEmpty ? computeDefaultSubstrateAddress(address) @@ -1138,8 +1150,12 @@ export abstract class BaseProvider extends AbstractProvider { getEvmAddress = async (substrateAddress: string, blockTag?: BlockTag): Promise => { const blockHash = await this._getBlockHash(blockTag); - const apiAt = await apiCache.getApiAt(this.api, blockHash); - const evmAddress = await apiAt.query.evmAccounts.evmAddresses(substrateAddress); + const evmAddress = await queryStorage>( + this.api, + 'evmAccounts.evmAddresses', + [substrateAddress], + blockHash + ); return getAddress(evmAddress.isEmpty ? computeDefaultEvmAddress(substrateAddress) : evmAddress.toString()); }; @@ -1155,8 +1171,12 @@ export abstract class BaseProvider extends AbstractProvider { this._getBlockHash(blockTag), ]); - const apiAt = await apiCache.getApiAt(this.api, blockHash); - const accountInfo = await apiAt.query.evm.accounts(address); + const accountInfo = await queryStorage>( + this.api, + 'evm.accounts', + [address], + blockHash + ); return accountInfo.unwrapOr(null); }; diff --git a/packages/eth-providers/src/utils/parseBlock.ts b/packages/eth-providers/src/utils/parseBlock.ts index 2973a336e..1eef66458 100644 --- a/packages/eth-providers/src/utils/parseBlock.ts +++ b/packages/eth-providers/src/utils/parseBlock.ts @@ -13,6 +13,7 @@ import { import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { GenericExtrinsic } from '@polkadot/types'; import { TransactionReceipt } from '@ethersproject/abstract-provider'; +import { Vec } from '@polkadot/types-codec'; import { BIGNUMBER_ZERO, ONE_HUNDRED_GWEI } from '../consts'; import { apiCache } from './ApiAtCache'; @@ -32,17 +33,16 @@ import { isTxFeeEvent, nativeToEthDecimal, } from './utils'; +import { queryStorage } from './queryStoarge'; export const getAllReceiptsAtBlock = async ( api: ApiPromise, blockHash: string, targetTxHash?: string ): Promise => { - const apiAt = await apiCache.getApiAt(api, blockHash); - const [block, blockEvents] = await Promise.all([ api.rpc.chain.getBlock(blockHash), - apiAt.query.system.events(), + queryStorage>(api, 'system.events', [], blockHash), ]); return await parseReceiptsFromBlockData(api, block, blockEvents, targetTxHash, true); diff --git a/packages/eth-providers/src/utils/queryStoarge.ts b/packages/eth-providers/src/utils/queryStoarge.ts new file mode 100644 index 000000000..cca533ef4 --- /dev/null +++ b/packages/eth-providers/src/utils/queryStoarge.ts @@ -0,0 +1,53 @@ +import { ApiPromise } from '@polkadot/api'; +import { Codec } from '@polkadot/types/types'; +import { decorateStorage, unwrapStorageType } from '@polkadot/types'; +import { u8aToU8a } from '@polkadot/util'; + +export const queryStorage = async ( + api: ApiPromise, + module: `${string}.${string}`, + args: any[], + blockHash: string, +): Promise => { + const registry = await api.getBlockRegistry(u8aToU8a(blockHash)); + + const storage = decorateStorage( + registry.registry, + registry.metadata.asLatest, + registry.metadata.version, + ); + + const [section, method] = module.split('.'); + + const entry = storage[section][method]; + const key = entry(...args); + + const outputType = unwrapStorageType( + registry.registry, + entry.meta.type, + entry.meta.modifier.isOptional, + ); + + + const value: any = await api.rpc.state.getStorage(key, blockHash); + + // we convert to Uint8Array since it maps to the raw encoding, all + // data will be correctly encoded (incl. numbers, excl. :code) + const input = value === null + ? null + : u8aToU8a( + entry.meta.modifier.isOptional + ? value.toU8a() + : value.isSome + ? value.unwrap().toU8a() + : null, + ); + + + const result = registry.registry.createTypeUnsafe(outputType, [input], { + blockHash, + isPedantic: !entry.meta.modifier.isOptional, + }); + + return result; +};