update signature of doublemap storage function, add e2e test cases (#957)
* update signature of doublemap storage function * add test cases * update doublemap implementation * update implementation * remove duplicate test case * update test cases
This commit is contained in:
+29
-15
@@ -573,8 +573,11 @@ export default abstract class ApiBase<CodecResult, SubscriptionResult> implement
|
||||
}
|
||||
|
||||
return onCall(
|
||||
(arg: CodecArg) => this._rpcRx.state
|
||||
.subscribeStorage([[creator, arg]])
|
||||
(...args: CodecArg[]) => this._rpcRx.state
|
||||
.subscribeStorage([
|
||||
creator.meta.type.isDoubleMap
|
||||
? [creator, args]
|
||||
: [creator, ...args]])
|
||||
.pipe(
|
||||
// state_storage returns an array of values, since we have just subscribed to
|
||||
// a single entry, we pull that from the array and return it as-is
|
||||
@@ -589,37 +592,48 @@ export default abstract class ApiBase<CodecResult, SubscriptionResult> implement
|
||||
|
||||
decorated.creator = creator;
|
||||
|
||||
decorated.at = (hash: Hash | Uint8Array | string, arg?: CodecArg): C =>
|
||||
decorated.at = (hash: Hash | Uint8Array | string, arg1?: CodecArg, arg2?: CodecArg): C =>
|
||||
onCall(
|
||||
(arg: CodecArg) => this._rpcRx.state.getStorage([creator, arg], hash),
|
||||
[arg]
|
||||
(arg1?: CodecArg, arg2?: CodecArg) => this._rpcRx.state.getStorage(
|
||||
creator.meta.type.isDoubleMap
|
||||
? [creator, [arg1, arg2]]
|
||||
: [creator, arg1],
|
||||
hash),
|
||||
[arg1, arg2]
|
||||
) as C;
|
||||
|
||||
// FIXME The unknown cast is needed since the onCall result, `C | S` cannot
|
||||
// be converted from C to the actual result required
|
||||
decorated.hash = (arg?: CodecArg): HashResult<C, S> =>
|
||||
decorated.hash = (arg1?: CodecArg, arg2?: CodecArg): HashResult<C, S> =>
|
||||
onCall(
|
||||
(arg: CodecArg) => this._rpcRx.state.getStorageHash([creator, arg]),
|
||||
[arg]
|
||||
(arg1?: CodecArg, arg2?: CodecArg) => this._rpcRx.state.getStorageHash(
|
||||
creator.meta.type.isDoubleMap
|
||||
? [creator, [arg1, arg2]]
|
||||
: [creator, arg1]),
|
||||
[arg1, arg2]
|
||||
) as unknown as HashResult<C, S>;
|
||||
|
||||
decorated.key = (arg?: CodecArg): string =>
|
||||
u8aToHex(compactStripLength(creator(arg))[1]);
|
||||
decorated.key = (arg1?: CodecArg, arg2?: CodecArg): string =>
|
||||
u8aToHex(compactStripLength(creator(creator.meta.type.isDoubleMap ? [arg1, arg2] : arg1))[1]);
|
||||
|
||||
decorated.multi = (args: Array<CodecArg>, callback?: CodecCallback): S =>
|
||||
// When using double map storage function, user need to path double map key as an array
|
||||
decorated.multi = (args: Array<CodecArg[] | CodecArg>, callback?: CodecCallback): S =>
|
||||
onCall(
|
||||
() =>
|
||||
this._rpcRx.state
|
||||
.subscribeStorage(args.map((arg) => [creator, arg]))
|
||||
.subscribeStorage(args.map((arg: CodecArg[] | CodecArg) => [creator, arg]))
|
||||
.pipe(map((results) => new VectorAny(...results))),
|
||||
[],
|
||||
callback
|
||||
) as unknown as S;
|
||||
|
||||
decorated.size = (arg?: CodecArg): U64Result<C, S> =>
|
||||
decorated.size = (arg1?: CodecArg, arg2?: CodecArg): U64Result<C, S> =>
|
||||
onCall(
|
||||
(arg: CodecArg) => this._rpcRx.state.getStorageSize([creator, arg]),
|
||||
[arg]
|
||||
(arg1?: CodecArg, arg2?: CodecArg) => this._rpcRx.state.getStorageSize(
|
||||
creator.meta.type.isDoubleMap
|
||||
? [creator, [arg1, arg2]]
|
||||
: [creator, arg1]),
|
||||
[arg1, arg2]
|
||||
) as unknown as U64Result<C, S>;
|
||||
|
||||
return this.decorateFunctionMeta(creator, decorated) as QueryableStorageFunction<C, S>;
|
||||
|
||||
@@ -48,18 +48,19 @@ export type U64Result<CodecResult, SubscriptionResult> =
|
||||
: Promise<U64>;
|
||||
|
||||
export interface QueryableStorageFunctionBase<CodecResult, SubscriptionResult> extends StorageFunction {
|
||||
(arg?: CodecArg): CodecResult;
|
||||
at: (hash: Hash | Uint8Array | string, arg?: CodecArg) => CodecResult;
|
||||
(arg1?: CodecArg, arg2?: CodecArg): CodecResult;
|
||||
at: (hash: Hash | Uint8Array | string, arg1?: CodecArg, arg2?: CodecArg) => CodecResult;
|
||||
creator: StorageFunction;
|
||||
hash: (arg?: CodecArg) => HashResult<CodecResult, SubscriptionResult>;
|
||||
key: (arg?: CodecArg) => string;
|
||||
multi: (args: Array<CodecArg>, callback?: CodecCallback) => SubscriptionResult;
|
||||
size: (arg?: CodecArg) => U64Result<CodecResult, SubscriptionResult>;
|
||||
hash: (arg1?: CodecArg, arg2?: CodecArg) => HashResult<CodecResult, SubscriptionResult>;
|
||||
key: (arg1?: CodecArg, arg2?: CodecArg) => string;
|
||||
multi: (args: Array<CodecArg[] | CodecArg>, callback?: CodecCallback) => SubscriptionResult;
|
||||
size: (arg1?: CodecArg, arg2?: CodecArg) => U64Result<CodecResult, SubscriptionResult>;
|
||||
}
|
||||
|
||||
interface QueryableStorageFunctionPromise<CodecResult, SubscriptionResult> extends QueryableStorageFunctionBase<CodecResult, SubscriptionResult> {
|
||||
(callback: CodecCallback): SubscriptionResult;
|
||||
(arg: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||
(arg1: CodecArg, arg2: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||
}
|
||||
|
||||
export type QueryableStorageFunction<CodecResult, SubscriptionResult> =
|
||||
|
||||
@@ -7,7 +7,7 @@ import BN from 'bn.js';
|
||||
import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||
import { LinkageResult } from '@polkadot/types/codec/Linkage';
|
||||
import { EventRecord, Header, Vector } from '@polkadot/types';
|
||||
import { Balance, EventRecord, Header, Option, Vector } from '@polkadot/types';
|
||||
|
||||
import Api from './../../src/promise';
|
||||
|
||||
@@ -177,20 +177,197 @@ describe.skip('Promise e2e queries', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('queries state using double map key', async () => {
|
||||
// TODO Update ['any', '0x1234'] to the key of a known event topic and update '[]' to the expected value
|
||||
const eventTopics = await api.query.system.eventTopics(['any', '0x1234']);
|
||||
describe('with plain type', () => {
|
||||
const EXISTENTIAL_DEPOSIT = 500;
|
||||
it('queries correct value', async () => {
|
||||
const existentialDeposit = await api.query.balances.existentialDeposit() as Balance;
|
||||
|
||||
expect(eventTopics.toString()).toEqual('[]');
|
||||
expect(existentialDeposit.toNumber()).toEqual(EXISTENTIAL_DEPOSIT);
|
||||
});
|
||||
|
||||
it('queries correct value at a specified block', async () => {
|
||||
const header = await api.rpc.chain.getHeader() as Header;
|
||||
const existentialDepositAt = await api.query.balances.existentialDeposit.at(header.hash) as Balance;
|
||||
|
||||
expect(existentialDepositAt.toNumber()).toEqual(EXISTENTIAL_DEPOSIT);
|
||||
});
|
||||
|
||||
it('subscribes to query and get correct result', (done) => {
|
||||
return api.query.balances.existentialDeposit((existentialDeposit) => {
|
||||
expect(existentialDeposit.toNumber()).toEqual(EXISTENTIAL_DEPOSIT);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('queries correct hash', async () => {
|
||||
const hash = await api.query.balances.existentialDeposit.hash();
|
||||
|
||||
expect(hash).toBeDefined();
|
||||
});
|
||||
|
||||
it('gets correct key', async () => {
|
||||
const key = api.query.balances.existentialDeposit.key();
|
||||
const existentialDepositData = await api.rpc.state.getStorage(key) as Option<any>;
|
||||
const existentialDepositRPC = new Balance(existentialDepositData.unwrapOr(undefined));
|
||||
|
||||
expect(existentialDepositRPC.toNumber()).toEqual(EXISTENTIAL_DEPOSIT);
|
||||
});
|
||||
|
||||
it('queries correct size', async () => {
|
||||
const size = await api.query.balances.existentialDeposit.size();
|
||||
|
||||
expect(size.toNumber()).not.toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes to queries using double map key', async (done) => {
|
||||
return (
|
||||
// TODO Update ['any', '0x1234'] to the key of a known event topic and update '[]' to the expected value
|
||||
api.query.system.eventTopics(['any', '0x1234'], (eventTopics) => {
|
||||
expect(eventTopics.toString()).toEqual('[]');
|
||||
describe('with map type', () => {
|
||||
it('queries correct value', async () => {
|
||||
const balance = await api.query.balances.freeBalance(keyring.alice.address()) as Balance;
|
||||
|
||||
expect(balance.isZero()).toBe(false);
|
||||
});
|
||||
|
||||
it('queries correct value at a specified block', async () => {
|
||||
// assume the account Alice is only used in test(the balance of Alice does not change in this test case)
|
||||
const balance = await api.query.balances.freeBalance(keyring.alice.address());
|
||||
const header = await api.rpc.chain.getHeader() as Header;
|
||||
const balanceAt = await api.query.balances.freeBalance.at(header.hash, keyring.alice.address()) as Balance;
|
||||
|
||||
expect(balanceAt.isZero()).toBe(false);
|
||||
expect(balanceAt.toString()).toEqual(balance.toString());
|
||||
});
|
||||
|
||||
it('subscribes to query and get correct result', async (done) => {
|
||||
// assume the account Alice is only used in test(the balance of Alice does not change in this test case)
|
||||
const balance = await api.query.balances.freeBalance(keyring.alice.address());
|
||||
|
||||
return api.query.balances.freeBalance(keyring.alice.address(), (balanceSubscribed) => {
|
||||
expect(balanceSubscribed.isZero()).toBe(false);
|
||||
expect(balanceSubscribed.toString()).toEqual(balance.toString());
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('queries correct hash', async () => {
|
||||
const hash = await api.query.balances.freeBalance.hash(keyring.alice.address());
|
||||
|
||||
expect(hash).toBeDefined();
|
||||
});
|
||||
|
||||
it('gets correct key', async () => {
|
||||
// assume the account Alice is only used in test(the balance of Alice does not change in this test case)
|
||||
const key = api.query.balances.freeBalance.key(keyring.alice.address());
|
||||
const balanceData = await api.rpc.state.getStorage(key) as Option<any>;
|
||||
const balanceRPC = new Balance(balanceData.unwrapOr(undefined));
|
||||
|
||||
const balance = await api.query.balances.freeBalance(keyring.alice.address());
|
||||
|
||||
expect(balanceRPC.isZero()).toBe(false);
|
||||
expect(balanceRPC.toString()).toEqual(balance.toString());
|
||||
});
|
||||
|
||||
it('queries multiple results', async () => {
|
||||
// assume the account Alice and Bob are only used in test(the balance of them do not change in this test case)
|
||||
const balanceAlice = await api.query.balances.freeBalance(keyring.alice.address());
|
||||
const balanceBob = await api.query.balances.freeBalance(keyring.bob.address());
|
||||
|
||||
const balances = await api.query.balances.freeBalance.multi([
|
||||
keyring.alice.address(),
|
||||
keyring.bob.address()
|
||||
]);
|
||||
|
||||
expect(balances).toHaveLength(2);
|
||||
expect((balances as any)[0].toString()).toEqual(balanceAlice.toString());
|
||||
expect((balances as any)[1].toString()).toEqual(balanceBob.toString());
|
||||
});
|
||||
|
||||
it('subscribes to multiple queries and get correct results', async (done) => {
|
||||
// assume the account Alice and Bob are only used in test(the balance of them do not change in this test case)
|
||||
const balanceAlice = await api.query.balances.freeBalance(keyring.alice.address());
|
||||
const balanceBob = await api.query.balances.freeBalance(keyring.bob.address());
|
||||
|
||||
return api.query.balances.freeBalance.multi([
|
||||
keyring.alice.address(),
|
||||
keyring.bob.address()
|
||||
], (balances) => {
|
||||
expect(balances).toHaveLength(2);
|
||||
expect(balances[0].toString()).toEqual(balanceAlice.toString());
|
||||
expect(balances[1].toString()).toEqual(balanceBob.toString());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('queries correct size', async () => {
|
||||
const size = await api.query.balances.freeBalance.size(keyring.alice.address());
|
||||
|
||||
expect(size.toNumber()).not.toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO Update ['any', '0x1234'] to the key of a known event topic and update EXPECTED_VALUE to the expected value
|
||||
describe('with double map type', () => {
|
||||
const KEY1 = 'any';
|
||||
const KEY2 = '0x1234';
|
||||
it('queries correct value', async () => {
|
||||
const eventTopics = await api.query.system.eventTopics(KEY1, KEY2);
|
||||
|
||||
expect(eventTopics.toJSON()).toEqual([]);
|
||||
});
|
||||
|
||||
it('queries correct value at a specified block', async () => {
|
||||
const header = await api.rpc.chain.getHeader() as Header;
|
||||
|
||||
// TODO check: this will throw the error: Encoding for input doesn't match output, created 0x00 from 0x
|
||||
const eventTopicsAt = await api.query.system.eventTopics.at(header.hash, KEY1, KEY2);
|
||||
expect(eventTopicsAt).toEqual(undefined);
|
||||
|
||||
// const eventTopicsAt = await api.query.system.eventTopics.at(header.hash, KEY1, KEY2);
|
||||
// expect(eventTopicsAt.toJSON()).toEqual([]);
|
||||
});
|
||||
|
||||
it('subscribes to query and get correct result', async (done) => {
|
||||
return api.query.system.eventTopics(KEY1, KEY2, (eventTopicsAt) => {
|
||||
expect(eventTopicsAt.toJSON()).toEqual([]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('queries correct hash', async () => {
|
||||
const hash = await api.query.system.eventTopics(KEY1, KEY2);
|
||||
|
||||
expect(hash).toBeDefined();
|
||||
});
|
||||
|
||||
it('gets correct key', async () => {
|
||||
const key = api.query.system.eventTopics.key(KEY1, KEY2);
|
||||
const eventTopicsData = await api.rpc.state.getStorage(key) as Option<any>;
|
||||
|
||||
expect(eventTopicsData.unwrapOr(undefined)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('queries multiple results', async () => {
|
||||
const eventTopicsList = await api.query.system.eventTopics.multi([
|
||||
[KEY1, KEY2]
|
||||
]);
|
||||
|
||||
expect(eventTopicsList).toHaveLength(1);
|
||||
expect((eventTopicsList as any)[0].toJSON()).toEqual([]);
|
||||
});
|
||||
|
||||
it('subscribes to multiple queries and get correct results', async (done) => {
|
||||
return api.query.system.eventTopics.multi([
|
||||
[KEY1, KEY2]
|
||||
], (eventTopicsList) => {
|
||||
expect(eventTopicsList).toHaveLength(1);
|
||||
expect(eventTopicsList[0].toJSON()).toEqual([]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('queries correct size', async () => {
|
||||
const size = await api.query.system.eventTopics.size(KEY1, KEY2);
|
||||
|
||||
expect(size.toNumber()).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('createFunction', () => {
|
||||
|
||||
it('should return correct key', () => {
|
||||
const result = storageFn(['5DXUeE5N5LtkW97F2PzqYPyqNkxqSWESdGSPTX6AvkUAhwKP', '5DXUeE5N5LtkW97F2PzqYPyqNkxqSWESdGSPTX6AvkUAhwKP']);
|
||||
expect(u8aToHex(result)).toEqual('0x00fa40e72d7173e69ee54b980345ea01cb81e64258502e0247af4303dee91ec0aec2ecd3a60ab080cff7b52a8f6d543b');
|
||||
expect(u8aToHex(result)).toEqual('0xc000fa40e72d7173e69ee54b980345ea01cb81e64258502e0247af4303dee91ec0aec2ecd3a60ab080cff7b52a8f6d543b');
|
||||
});
|
||||
|
||||
it('needs two arguments', () => {
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function createFunction (section: Text | string, method: Text | s
|
||||
const param1Hashed = hasher(param1Encoded);
|
||||
const param2Hashed = key2Hasher(createType(type2, arg[1]).toU8a(true));
|
||||
|
||||
return u8aConcat(param1Hashed, param2Hashed);
|
||||
return Compact.addLengthPrefix(u8aConcat(param1Hashed, param2Hashed));
|
||||
}
|
||||
|
||||
if (meta.type.isMap) {
|
||||
|
||||
@@ -232,4 +232,18 @@ describe('createType', () => {
|
||||
createType('{"_enum": {"A": null, "B": "u32", "C": null} }', 1).toJSON()
|
||||
).toEqual({ B: 0 });
|
||||
});
|
||||
|
||||
it('throw error when create base is a StorageData with null value and isPedantic is true' , () => {
|
||||
const base = createType('StorageData', null);
|
||||
expect(
|
||||
() => createType('DoubleMap<Vec<(BlockNumber,EventIndex)>>', base, true)
|
||||
).toThrow(/Encoding for input doesn't match output, created 0x00 from 0x/);
|
||||
});
|
||||
|
||||
it('throw error when create base is a StorageData with null value and isPedantic is true' , () => {
|
||||
const base = createType('StorageData', null);
|
||||
expect(
|
||||
() => createType('Vec<(BlockNumber,EventIndex)>', base, true)
|
||||
).toThrow(/Encoding for input doesn't match output, created 0x00 from 0x/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import { assert, isFunction, isString, isU8a } from '@polkadot/util';
|
||||
|
||||
import U8a from '../codec/U8a';
|
||||
import { StorageFunctionMetadata as MetaV4 } from '../Metadata/v4/Storage';
|
||||
import { AnyU8a } from '../types';
|
||||
import Bytes from './Bytes';
|
||||
@@ -70,14 +69,6 @@ export default class StorageKey extends Bytes {
|
||||
|
||||
assert(isFunction(fn), 'Expected function input for key construction');
|
||||
|
||||
if (fn.meta && fn.meta.type.isDoubleMap) {
|
||||
return {
|
||||
key: new U8a(fn(...arg)), // skip compact length check in decodeBytes
|
||||
method: fn.method,
|
||||
section: fn.section
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key: fn(...arg),
|
||||
method: fn.method,
|
||||
|
||||
Reference in New Issue
Block a user