|
|
|
@@ -27,75 +27,75 @@ export interface CreateItemFn {
|
|
|
|
|
|
|
|
|
|
type CreateArgType = boolean | string | number | null | BN | Uint8Array | Codec;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* From the schema of a function in the module's storage, generate the function
|
|
|
|
|
* that will return the correct storage key.
|
|
|
|
|
*
|
|
|
|
|
* @param schema - The function's definition schema to create the function from.
|
|
|
|
|
* The schema is taken from state_getMetadata.
|
|
|
|
|
* @param options - Additional options when creating the function. These options
|
|
|
|
|
* are not known at runtime (from state_getMetadata), they need to be supplied
|
|
|
|
|
* by us manually at compile time.
|
|
|
|
|
*/
|
|
|
|
|
export default function createFunction ({ meta, method, prefix, section }: CreateItemFn, options: CreateItemOptions = {}): StorageEntry {
|
|
|
|
|
const NULL_HASHER = (value: Uint8Array): Uint8Array => value;
|
|
|
|
|
|
|
|
|
|
// with the prefix, method & options, create both the string & raw keys
|
|
|
|
|
function createKeys ({ method, prefix }: CreateItemFn, options: CreateItemOptions): [string, Uint8Array] {
|
|
|
|
|
const stringKey = options.key
|
|
|
|
|
? options.key
|
|
|
|
|
: `${prefix} ${method}`;
|
|
|
|
|
const rawKey = stringToU8a(stringKey);
|
|
|
|
|
|
|
|
|
|
// Get the hashing function
|
|
|
|
|
let hasher: HasherFunction;
|
|
|
|
|
let key2Hasher: HasherFunction;
|
|
|
|
|
return [
|
|
|
|
|
stringKey,
|
|
|
|
|
stringToU8a(stringKey)
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (meta.type.isDoubleMap) {
|
|
|
|
|
hasher = getHasher(meta.type.asDoubleMap.hasher);
|
|
|
|
|
key2Hasher = getHasher(meta.type.asDoubleMap.key2Hasher);
|
|
|
|
|
} else if (meta.type.isMap) {
|
|
|
|
|
hasher = getHasher(meta.type.asMap.hasher);
|
|
|
|
|
} else {
|
|
|
|
|
hasher = getHasher();
|
|
|
|
|
// get the hashers, the base (and in the case of DoubleMap), the second key
|
|
|
|
|
function getHashers ({ meta: { type } }: CreateItemFn): [HasherFunction, HasherFunction?] {
|
|
|
|
|
if (type.isDoubleMap) {
|
|
|
|
|
return [
|
|
|
|
|
getHasher(type.asDoubleMap.hasher),
|
|
|
|
|
getHasher(type.asDoubleMap.key2Hasher)
|
|
|
|
|
];
|
|
|
|
|
} else if (type.isMap) {
|
|
|
|
|
return [getHasher(type.asMap.hasher)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Can only have zero or one argument:
|
|
|
|
|
// - storage.balances.freeBalance(address)
|
|
|
|
|
// - storage.timestamp.blockPeriod()
|
|
|
|
|
// For doublemap queries the params is passed in as an tuple, [key1, key2]
|
|
|
|
|
const _storageFn = (arg?: CreateArgType | [CreateArgType?, CreateArgType?]): Uint8Array => {
|
|
|
|
|
let key = rawKey;
|
|
|
|
|
// the default
|
|
|
|
|
return [getHasher()];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (meta.type.isDoubleMap) {
|
|
|
|
|
assert(Array.isArray(arg) && !isUndefined(arg[0]) && !isNull(arg[0]) && !isUndefined(arg[1]) && !isNull(arg[1]), `${meta.name} expects two arguments`);
|
|
|
|
|
// create a key for a DoubleMap type
|
|
|
|
|
function createKeyDoubleMap ({ meta: { name, type } }: CreateItemFn, rawKey: Uint8Array, args: [CreateArgType, CreateArgType], [hasher, key2Hasher]: [HasherFunction, HasherFunction?]): Uint8Array {
|
|
|
|
|
// since we are passing an almost-unknown through, trust, but verify
|
|
|
|
|
assert(
|
|
|
|
|
Array.isArray(args) && !isUndefined(args[0]) && !isNull(args[0]) && !isUndefined(args[1]) && !isNull(args[1]),
|
|
|
|
|
`${name} is a DoubleMap and requires two arguments`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// we have checked that it is an array in the assert, so all ok
|
|
|
|
|
const [key1, key2] = arg as [CreateArgType, CreateArgType];
|
|
|
|
|
const type1 = meta.type.asDoubleMap.key1.toString();
|
|
|
|
|
const type2 = meta.type.asDoubleMap.key2.toString();
|
|
|
|
|
const param1Encoded = u8aConcat(key, createTypeUnsafe(type1, [key1]).toU8a(true));
|
|
|
|
|
const param1Hashed = hasher(param1Encoded);
|
|
|
|
|
const param2Hashed = key2Hasher(createTypeUnsafe(type2, [key2]).toU8a(true));
|
|
|
|
|
const [key1, key2] = args;
|
|
|
|
|
const type1 = type.asDoubleMap.key1.toString();
|
|
|
|
|
const type2 = type.asDoubleMap.key2.toString();
|
|
|
|
|
const param1Encoded = u8aConcat(rawKey, createTypeUnsafe(type1, [key1]).toU8a(true));
|
|
|
|
|
const param1Hashed = hasher(param1Encoded);
|
|
|
|
|
|
|
|
|
|
return Compact.addLengthPrefix(u8aConcat(param1Hashed, param2Hashed));
|
|
|
|
|
}
|
|
|
|
|
// @ts-ignore If this fails it means the getHashers function failed - and we have much bigger issues
|
|
|
|
|
const param2Hashed = key2Hasher(createTypeUnsafe(type2, [key2]).toU8a(true));
|
|
|
|
|
|
|
|
|
|
if (meta.type.isMap) {
|
|
|
|
|
assert(!isUndefined(arg) && !isNull(arg), `${meta.name} expects one argument`);
|
|
|
|
|
// as per createKey, always add the length prefix (underlying it is Bytes)
|
|
|
|
|
return Compact.addLengthPrefix(u8aConcat(param1Hashed, param2Hashed));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const type = meta.type.asMap.key.toString();
|
|
|
|
|
const param = createTypeUnsafe(type, [arg]).toU8a();
|
|
|
|
|
// create a key for either a map or a plain value
|
|
|
|
|
function createKey ({ meta: { name, type } }: CreateItemFn, rawKey: Uint8Array, arg: CreateArgType, hasher: (value: Uint8Array) => Uint8Array): Uint8Array {
|
|
|
|
|
let key = rawKey;
|
|
|
|
|
|
|
|
|
|
key = u8aConcat(key, param);
|
|
|
|
|
}
|
|
|
|
|
if (type.isMap) {
|
|
|
|
|
assert(!isUndefined(arg) && !isNull(arg), `${name} is a Map and requires one argument`);
|
|
|
|
|
|
|
|
|
|
// StorageKey is a Bytes, so is length-prefixed
|
|
|
|
|
return Compact.addLengthPrefix(
|
|
|
|
|
options.skipHashing
|
|
|
|
|
? key
|
|
|
|
|
: hasher(key)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
const mapType = type.asMap.key.toString();
|
|
|
|
|
const param = createTypeUnsafe(mapType, [arg]).toU8a();
|
|
|
|
|
|
|
|
|
|
const storageFn = _storageFn as StorageEntry;
|
|
|
|
|
key = u8aConcat(key, param);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// StorageKey is a Bytes, so is length-prefixed
|
|
|
|
|
return Compact.addLengthPrefix(hasher(key));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// attach the metadata to expsnd to a StorageFunction
|
|
|
|
|
function expandWithMeta ({ meta, method, prefix, section }: CreateItemFn, storageFn: StorageEntry): StorageEntry {
|
|
|
|
|
storageFn.meta = meta;
|
|
|
|
|
storageFn.method = stringLowerFirst(method);
|
|
|
|
|
storageFn.prefix = prefix;
|
|
|
|
@@ -103,28 +103,68 @@ export default function createFunction ({ meta, method, prefix, section }: Creat
|
|
|
|
|
|
|
|
|
|
// explicitly add the actual method in the toJSON, this gets used to determine caching and without it
|
|
|
|
|
// instances (e.g. collective) will not work since it is only matched on param meta
|
|
|
|
|
storageFn.toJSON = (): any => ({ ...(meta.toJSON() as any), storage: { method, prefix, section } });
|
|
|
|
|
storageFn.toJSON = (): any => ({
|
|
|
|
|
...(meta.toJSON() as any),
|
|
|
|
|
storage: { method, prefix, section }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (meta.type.isMap && meta.type.asMap.linked.isTrue) {
|
|
|
|
|
const headHash = new U8a(hasher(`head of ${stringKey}`));
|
|
|
|
|
const headFn: any = (): U8a => headHash;
|
|
|
|
|
return storageFn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// metadata with a fallback value using the type of the key, the normal
|
|
|
|
|
// meta fallback only applies to actual entry values, create one for head
|
|
|
|
|
headFn.meta = new StorageEntryMetadata({
|
|
|
|
|
name: meta.name,
|
|
|
|
|
modifier: createType('StorageEntryModifierV7', 1), // required
|
|
|
|
|
type: new StorageEntryType(createType('PlainTypeV7', meta.type.asMap.key), 0),
|
|
|
|
|
fallback: new Bytes(createTypeUnsafe(meta.type.asMap.key.toString()).toHex()),
|
|
|
|
|
documentation: meta.documentation
|
|
|
|
|
});
|
|
|
|
|
// attch the head key hashing for linked maps
|
|
|
|
|
function extendLinkedMap ({ meta: { documentation, name, type } }: CreateItemFn, storageFn: StorageEntry, stringKey: string, hasher: HasherFunction): StorageEntry {
|
|
|
|
|
const headHash = new U8a(hasher(`head of ${stringKey}`));
|
|
|
|
|
const headFn: any = (): U8a =>
|
|
|
|
|
headHash;
|
|
|
|
|
|
|
|
|
|
// here we pass the section/method through as well - these are not on
|
|
|
|
|
// the function itself, so specify these explicitly to the constructor
|
|
|
|
|
storageFn.headKey = new StorageKey(headFn, {
|
|
|
|
|
method: storageFn.method,
|
|
|
|
|
section: `head of ${storageFn.section}`
|
|
|
|
|
});
|
|
|
|
|
// metadata with a fallback value using the type of the key, the normal
|
|
|
|
|
// meta fallback only applies to actual entry values, create one for head
|
|
|
|
|
headFn.meta = new StorageEntryMetadata({
|
|
|
|
|
name,
|
|
|
|
|
modifier: createType('StorageEntryModifierV7', 1), // required
|
|
|
|
|
type: new StorageEntryType(createType('PlainTypeV7', type.asMap.key), 0),
|
|
|
|
|
fallback: new Bytes(createTypeUnsafe(type.asMap.key.toString()).toHex()),
|
|
|
|
|
documentation
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// here we pass the section/method through as well - these are not on
|
|
|
|
|
// the function itself, so specify these explicitly to the constructor
|
|
|
|
|
storageFn.headKey = new StorageKey(headFn, {
|
|
|
|
|
method: storageFn.method,
|
|
|
|
|
section: `head of ${storageFn.section}`
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return storageFn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* From the schema of a function in the module's storage, generate the function
|
|
|
|
|
* that will return the correct storage key.
|
|
|
|
|
*
|
|
|
|
|
* @param item - The function's definition schema to create the function from.
|
|
|
|
|
* The schema is taken from state_getMetadata.
|
|
|
|
|
* @param options - Additional options when creating the function. These options
|
|
|
|
|
* are not known at runtime (from state_getMetadata), they need to be supplied
|
|
|
|
|
* by us manually at compile time.
|
|
|
|
|
*/
|
|
|
|
|
export default function createFunction (item: CreateItemFn, options: CreateItemOptions = {}): StorageEntry {
|
|
|
|
|
const { meta: { type } } = item;
|
|
|
|
|
const [stringKey, rawKey] = createKeys(item, options);
|
|
|
|
|
const [hasher, key2Hasher] = getHashers(item);
|
|
|
|
|
|
|
|
|
|
// Can only have zero or one argument:
|
|
|
|
|
// - storage.balances.freeBalance(address)
|
|
|
|
|
// - storage.timestamp.blockPeriod()
|
|
|
|
|
// For doublemap queries the params is passed in as an tuple, [key1, key2]
|
|
|
|
|
const _storageFn = (arg?: CreateArgType | [CreateArgType?, CreateArgType?]): Uint8Array =>
|
|
|
|
|
type.isDoubleMap
|
|
|
|
|
? createKeyDoubleMap(item, rawKey, arg as [CreateArgType, CreateArgType], [hasher, key2Hasher])
|
|
|
|
|
: createKey(item, rawKey, arg as CreateArgType, options.skipHashing ? NULL_HASHER : hasher);
|
|
|
|
|
|
|
|
|
|
const storageFn = expandWithMeta(item, _storageFn as StorageEntry);
|
|
|
|
|
|
|
|
|
|
if (type.isMap && type.asMap.linked.isTrue) {
|
|
|
|
|
extendLinkedMap(item, storageFn, stringKey, hasher);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return storageFn;
|
|
|
|
|