UNPKG

@taquito/michelson-encoder

Version:

converts michelson data and types into convenient JS/TS objects

205 lines (204 loc) 7.4 kB
"use strict"; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.MichelsonMap = exports.MapTypecheckError = exports.InvalidMapTypeError = void 0; const storage_1 = require("./schema/storage"); const fast_json_stable_stringify_1 = require("fast-json-stable-stringify"); const core_1 = require("@taquito/core"); /** * @category Error * @description Error that indicates an invalid map type being passed or used */ class InvalidMapTypeError extends core_1.TaquitoError { constructor(mapType, reason) { super(); this.mapType = mapType; this.reason = reason; this.message = `The map type '${JSON.stringify(mapType)}' is invalid. Reason: ${reason}.`; this.name = 'InvalidMapTypeError'; } } exports.InvalidMapTypeError = InvalidMapTypeError; // Retrieve a unique symbol associated with the key from the environment // Used in order to identify all object that are of type MichelsonMap even if they come from different module const michelsonMapTypeSymbol = Symbol.for('taquito-michelson-map-type-symbol'); /** * * @throws {@link InvalidMapTypeError} when the argument passed to mapType is not a valid map type */ function validateMapType(value) { if (!('prim' in value)) { throw new InvalidMapTypeError(value, `Missing 'prim' field`); } if (!['map', 'big_map'].includes(value.prim)) { throw new InvalidMapTypeError(value, `The prim field should be 'map' or 'big_map'`); } if (!('args' in value)) { throw new InvalidMapTypeError(value, `Missing 'args' field`); } if (!Array.isArray(value.args)) { throw new InvalidMapTypeError(value, `The 'args' field should be an array`); } if (value.args.length !== 2) { throw new InvalidMapTypeError(value, `The 'args' field should have 2 elements`); } } /** * @category Error * @description Error that indicates a map type mismatch, where an attempt to set a key or value in a Map doesn't match the defined type of the Map */ class MapTypecheckError extends core_1.TaquitoError { constructor(value, type, objectType, reason) { super(); this.value = value; this.type = type; this.reason = reason; this.name = 'MapTypecheckError'; this.message = `The ${objectType} provided: ${JSON.stringify(value)} is not compatible with the expected michelson type: ${JSON.stringify(type)}. Reason: ${JSON.stringify(reason)}.`; this.name = 'MapTypecheckError'; } } exports.MapTypecheckError = MapTypecheckError; /** * @description Michelson Map is an abstraction over the michelson native map. It supports complex Pair as key */ class MichelsonMap { // Used to check if an object is a michelson map. // Using instanceof was not working for project that had multiple instance of taquito dependencies // as the class constructor is different static isMichelsonMap(obj) { return obj && obj[michelsonMapTypeSymbol] === true; } /** * @param mapType If specified key and value will be type-checked before being added to the map * * @example new MichelsonMap({ prim: "map", args: [{prim: "string"}, {prim: "int"}]}) */ constructor(mapType) { this.valueMap = new Map(); this.keyMap = new Map(); this[_a] = true; if (mapType) { this.setType(mapType); } } setType(mapType) { validateMapType(mapType); this.keySchema = new storage_1.Schema(mapType.args[0]); this.valueSchema = new storage_1.Schema(mapType.args[1]); } removeType() { this.keySchema = undefined; this.valueSchema = undefined; } static fromLiteral(obj, mapType) { const map = new MichelsonMap(mapType); Object.keys(obj).forEach((key) => { map.set(key, obj[key]); }); return map; } typecheckKey(key) { if (!this.keySchema) { return; } this.keySchema.Typecheck(key); } typecheckValue(value) { if (!this.valueSchema) { return; } this.valueSchema.Typecheck(value); } /** * @throws {@link MapTypecheckError} when the argument passed does not match the expected schema for value */ assertTypecheckValue(value) { try { this.typecheckValue(value); } catch (e) { throw new MapTypecheckError(value, this.valueSchema, 'value', e); } } /** * @throws {@link MapTypecheckError} when the argument passed does not match the expected schema for key */ assertTypecheckKey(key) { try { this.typecheckKey(key); } catch (e) { throw new MapTypecheckError(key, this.keySchema, 'key', e); } } serializeDeterministically(key) { return (0, fast_json_stable_stringify_1.default)(key); } *keys() { for (const [key] of this.entries()) { yield key; } } *values() { for (const [, value] of this.entries()) { yield value; } } *entries() { for (const key of this.valueMap.keys()) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion yield [this.keyMap.get(key), this.valueMap.get(key)]; } } get(key) { this.assertTypecheckKey(key); const strKey = this.serializeDeterministically(key); return this.valueMap.get(strKey); } /** * * @description Set a key and a value in the MichelsonMap. If the key already exists, override the current value. * * @example map.set("myKey", "myValue") // Using a string as key * * @example map.set({0: "test", 1: "test1"}, "myValue") // Using a pair as key * * @warn The same key can be represented in multiple ways, depending on the type of the key. This duplicate key situation will cause a runtime error (duplicate key) when sending the map data to the Tezos RPC node. * * For example, consider a contract with a map whose key is of type boolean. If you set the following values in MichelsonMap: map.set(false, "myValue") and map.set(null, "myValue"). * * You will get two unique entries in the MichelsonMap. These values will both be evaluated as falsy by the MichelsonEncoder and ultimately rejected by the Tezos RPC. */ set(key, value) { this.assertTypecheckKey(key); this.assertTypecheckValue(value); const strKey = this.serializeDeterministically(key); this.keyMap.set(strKey, key); this.valueMap.set(strKey, value); } delete(key) { this.assertTypecheckKey(key); this.keyMap.delete(this.serializeDeterministically(key)); this.valueMap.delete(this.serializeDeterministically(key)); } has(key) { this.assertTypecheckKey(key); const strKey = this.serializeDeterministically(key); return this.keyMap.has(strKey) && this.valueMap.has(strKey); } clear() { this.keyMap.clear(); this.valueMap.clear(); } get size() { return this.keyMap.size; } forEach(cb) { for (const [key, value] of this.entries()) { cb(value, key, this); } } } exports.MichelsonMap = MichelsonMap; _a = michelsonMapTypeSymbol;