Fix linked maps with defaults (#1162)
* isPedantic matches [] with isEmpty * Fix undefined.undefined on headKey * Adjust fallback for linked maps * Add tests for the linked map fallbacks (head and value)
This commit is contained in:
@@ -60,7 +60,6 @@ export default function createFunction ({ meta, method, prefix, section }: Creat
|
||||
assert(!isUndefined(arg) && !isNull(arg) && !isUndefined(arg[0]) && !isNull(arg[0]) && !isUndefined(arg[1]) && !isNull(arg[1]), `${meta.name} expects two arguments`);
|
||||
const type1 = meta.type.asDoubleMap.key1.toString();
|
||||
const type2 = meta.type.asDoubleMap.key2.toString();
|
||||
|
||||
const param1Encoded = u8aConcat(key, createType(type1, arg[0]).toU8a(true));
|
||||
const param1Hashed = hasher(param1Encoded);
|
||||
const param2Hashed = key2Hasher(createType(type2, arg[1]).toU8a(true));
|
||||
@@ -87,24 +86,38 @@ export default function createFunction ({ meta, method, prefix, section }: Creat
|
||||
|
||||
const storageFn = _storageFn as StorageEntry;
|
||||
|
||||
if (meta.type.isMap && meta.type.asMap.isLinked) {
|
||||
const keyHash = new U8a(hasher(`head of ${stringKey}`));
|
||||
const keyFn: any = (): U8a => keyHash;
|
||||
keyFn.meta = new StorageEntryMetadata({
|
||||
name: meta.name,
|
||||
modifier: new StorageEntryModifier('Required'),
|
||||
type: new StorageEntryType(new PlainType(meta.type.asMap.key), 0),
|
||||
fallback: new Bytes(),
|
||||
documentation: meta.documentation
|
||||
});
|
||||
storageFn.headKey = new StorageKey(keyFn);
|
||||
}
|
||||
|
||||
storageFn.meta = meta;
|
||||
storageFn.method = stringLowerFirst(method);
|
||||
storageFn.prefix = prefix;
|
||||
storageFn.section = section;
|
||||
storageFn.toJSON = (): any => meta.toJSON();
|
||||
|
||||
if (meta.type.isMap && meta.type.asMap.isLinked) {
|
||||
const keyHash = new U8a(hasher(`head of ${stringKey}`));
|
||||
const keyFn: any = (): U8a => keyHash;
|
||||
|
||||
// metadata with a flabbcak value using the type of the key, the normal
|
||||
// meta fallback only applies to actual entry values, create one for head
|
||||
keyFn.meta = new StorageEntryMetadata({
|
||||
name: meta.name,
|
||||
modifier: new StorageEntryModifier('Required'),
|
||||
type: new StorageEntryType(new PlainType(meta.type.asMap.key), 0),
|
||||
fallback: new Bytes(createType(meta.type.asMap.key).toHex()),
|
||||
documentation: meta.documentation
|
||||
});
|
||||
|
||||
// here we pass the section/methos through as well - these are not on
|
||||
// the function itself, so specify these explicitly to the constructor
|
||||
storageFn.headKey = new StorageKey(keyFn, {
|
||||
method: storageFn.method,
|
||||
section: `head of ${storageFn.section}`
|
||||
});
|
||||
|
||||
// adjust the fallback value - the metadata only specifies the value
|
||||
// part, add a Linkage<Type> to the fallback aswell. The additional
|
||||
// bytes here is a represnettaion of teh Options for next/prev
|
||||
meta.set('fallback', new Bytes(meta.fallback.toHex().concat('0000')));
|
||||
}
|
||||
|
||||
return storageFn;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Codec } from '@polkadot/types/types';
|
||||
|
||||
import fromMetadata from '@polkadot/api-metadata/storage/fromMetadata';
|
||||
import { Storage } from '@polkadot/api-metadata/storage/types';
|
||||
import { Balance } from '@polkadot/types';
|
||||
import Metadata from '@polkadot/types/Metadata';
|
||||
import { injectDefinitions } from '@polkadot/types/Metadata/util/testUtil';
|
||||
import rpcMetadataV3 from '@polkadot/types/Metadata/v3/static';
|
||||
@@ -42,21 +43,32 @@ function formattingTests (version: string, storage: Storage, encodedValues: [str
|
||||
)
|
||||
),
|
||||
subscribe: jest.fn((type, method, params, cb): void => {
|
||||
// this emulates https://github.com/polkadot-js/api/issues/1051
|
||||
params[0][0] === CONTRACT_KEY
|
||||
? cb(null, {
|
||||
if (params[0][0] === CONTRACT_KEY) {
|
||||
// this emulates https://github.com/polkadot-js/api/issues/1051
|
||||
cb(null, {
|
||||
block: '0x2345',
|
||||
changes: [
|
||||
[CONTRACT_KEY, OPTION_BYTES_HEX]
|
||||
]
|
||||
})
|
||||
: cb(null, {
|
||||
});
|
||||
} else if (params[0][0] === ENC_ONE || params[0][0] === ENC_TWO) {
|
||||
// known values for the balanaces
|
||||
cb(null, {
|
||||
block: '0x1234',
|
||||
changes: [
|
||||
[ENC_ONE, '0x01020000000000000000000000000000'],
|
||||
[ENC_TWO, '0x02010000000000000000000000000000']
|
||||
]
|
||||
});
|
||||
} else {
|
||||
// return empty values for all else
|
||||
cb(null, {
|
||||
block: '0x3456',
|
||||
changes: [
|
||||
[params[0][0], null]
|
||||
]
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -93,8 +105,8 @@ function formattingTests (version: string, storage: Storage, encodedValues: [str
|
||||
[storage.balances.freeBalance, ADDR_ONE],
|
||||
[storage.balances.freeBalance, ADDR_TWO]
|
||||
]
|
||||
).subscribe((value: any): void => {
|
||||
console.error(value);
|
||||
).subscribe((value: Balance[]): void => {
|
||||
// console.error(value);
|
||||
|
||||
expect(
|
||||
provider.subscribe
|
||||
@@ -105,7 +117,7 @@ function formattingTests (version: string, storage: Storage, encodedValues: [str
|
||||
expect.anything()
|
||||
);
|
||||
expect(
|
||||
value.map((balance: BN): number =>
|
||||
value.map((balance): number =>
|
||||
balance.toNumber()
|
||||
)
|
||||
).toEqual([0x0201, 0x0102]);
|
||||
@@ -122,13 +134,50 @@ function formattingTests (version: string, storage: Storage, encodedValues: [str
|
||||
api.state
|
||||
.subscribeStorage([[call, '0x00']])
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.subscribe((value: any): void => {
|
||||
.subscribe((value: Codec[]): void => {
|
||||
expect(value).toHaveLength(1);
|
||||
// console.error(value);
|
||||
|
||||
// expect(value.toHex()).toBe(OPTION_BYTES_HEX);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles fallbacks for linked heads (new metadata only)', (done): void => {
|
||||
const headKey = storage.staking.validators && storage.staking.validators.headKey;
|
||||
|
||||
// skip for old
|
||||
if (!headKey) {
|
||||
return done();
|
||||
}
|
||||
|
||||
api.state
|
||||
.subscribeStorage([headKey])
|
||||
.subscribe((value: Codec[]): void => {
|
||||
expect(value).toHaveLength(1);
|
||||
// console.error('head fallback', value);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles fallbacks for linked maps (new metadata only)', (done): void => {
|
||||
const headKey = storage.staking.validators && storage.staking.validators.headKey;
|
||||
|
||||
// skip for old
|
||||
if (!headKey) {
|
||||
return done();
|
||||
}
|
||||
|
||||
api.state
|
||||
.subscribeStorage([[storage.staking.validators, '0x00']])
|
||||
.subscribe((value: Codec): void => {
|
||||
expect(value).toBeDefined();
|
||||
// console.error('linked falklback', value);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,8 @@ export default class Rpc implements RpcInterface {
|
||||
|
||||
const call = (...values: any[]): Observable<any> => {
|
||||
return new Observable((observer: Observer<any>): VoidCallback => {
|
||||
let subscriptionPromise: Promise<number | void>;
|
||||
// Have at least an empty promise, as used in the unsubscribe
|
||||
let subscriptionPromise: Promise<number | void> = Promise.resolve();
|
||||
|
||||
const errorHandler = (error: Error): void => {
|
||||
const message = this.createErrorMessage(method, error);
|
||||
@@ -195,9 +196,12 @@ export default class Rpc implements RpcInterface {
|
||||
observer.next(this.formatOutput(method, params, result));
|
||||
};
|
||||
|
||||
subscriptionPromise = this.provider
|
||||
.subscribe(subType, subName, paramsJson, update)
|
||||
.catch(errorHandler);
|
||||
// FIXME This is a work-around, provider.subscribe _should_ always return,
|
||||
// however in some cases `this.provider.subscribe.catch` yields ".catch of
|
||||
// undefined", so here we flatten via Promise.reolve (which doe sfollow)
|
||||
subscriptionPromise = Promise.resolve(
|
||||
this.provider.subscribe(subType, subName, paramsJson, update)
|
||||
).catch(errorHandler);
|
||||
} catch (error) {
|
||||
errorHandler(error);
|
||||
}
|
||||
@@ -217,7 +221,7 @@ export default class Rpc implements RpcInterface {
|
||||
memoized.delete(...values);
|
||||
|
||||
// Unsubscribe from provider
|
||||
subscriptionPromise && subscriptionPromise
|
||||
subscriptionPromise
|
||||
.then((subscriptionId): Promise<boolean> =>
|
||||
isNumber(subscriptionId)
|
||||
? this.provider.unsubscribe(subType, unsubName, subscriptionId)
|
||||
@@ -302,7 +306,7 @@ export default class Rpc implements RpcInterface {
|
||||
const meta = key.meta || EMPTY_META;
|
||||
|
||||
if (meta.type.isMap && meta.type.asMap.isLinked) {
|
||||
return createType(type, base, true);
|
||||
return createType(type, isNull ? meta.fallback : base, true);
|
||||
} else if (meta.modifier.isOptional) {
|
||||
return new Option(
|
||||
createClass(type),
|
||||
@@ -331,7 +335,7 @@ export default class Rpc implements RpcInterface {
|
||||
this._storageCache.set(hexKey, value);
|
||||
|
||||
if (meta.type.isMap && meta.type.asMap.isLinked) {
|
||||
return createType(type, value.unwrapOr(null), true);
|
||||
return createType(type, value.unwrapOr(meta.fallback), true);
|
||||
} else if (meta.modifier.isOptional) {
|
||||
return new Option(
|
||||
createClass(type),
|
||||
|
||||
@@ -24,6 +24,11 @@ interface Decoded {
|
||||
section?: string;
|
||||
}
|
||||
|
||||
interface StorageKeyExtra {
|
||||
method: string;
|
||||
section: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name StorageKey
|
||||
* @description
|
||||
@@ -39,15 +44,15 @@ export default class StorageKey extends Bytes {
|
||||
|
||||
private _section?: string;
|
||||
|
||||
public constructor (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any]) {
|
||||
public constructor (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any], override: Partial<StorageKeyExtra> = {}) {
|
||||
const { key, method, section } = StorageKey.decodeStorageKey(value);
|
||||
|
||||
super(key);
|
||||
|
||||
this._meta = StorageKey.getMeta(value as StorageKey);
|
||||
this._method = method;
|
||||
this._method = override.method || method;
|
||||
this._outputType = StorageKey.getType(value as StorageKey);
|
||||
this._section = section;
|
||||
this._section = override.section || section;
|
||||
}
|
||||
|
||||
public static decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any]): Decoded {
|
||||
|
||||
Reference in New Issue
Block a user