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:
Alex Wang
2019-05-29 16:14:42 +02:00
committed by Jaco Greeff
parent e1a54f2b42
commit a7f16064a9
7 changed files with 241 additions and 44 deletions
+29 -15
View File
@@ -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>;
+7 -6
View File
@@ -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> =
+189 -12
View File
@@ -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,