UNPKG

@taquito/michelson-encoder

Version:

Michelson encoding and decoding utilities for Taquito.

261 lines (260 loc) 9.63 kB
"use strict"; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.Schema = void 0; exports.deepEqual = deepEqual; const bigmap_1 = require("../tokens/bigmap"); const createToken_1 = require("../tokens/createToken"); const map_1 = require("../tokens/map"); const or_1 = require("../tokens/or"); const pair_1 = require("../tokens/pair"); const ticket_1 = require("../tokens/ticket"); const ticket_deprecated_1 = require("../tokens/ticket-deprecated"); const token_1 = require("../tokens/token"); const errors_1 = require("./errors"); const schemaTypeSymbol = Symbol.for('taquito-schema-type-symbol'); // collapse comb pair function collapse(val, prim = pair_1.PairToken.prim) { if (Array.isArray(val)) { return collapse({ prim: prim, args: val, }, prim); } const extended = val; if (extended.prim === prim && extended.args && extended.args.length > 2) { return { ...extended, args: [ extended.args?.[0], { prim: prim, args: extended.args?.slice(1), }, ], }; } return extended; } function deepEqual(a, b) { if (a === undefined || b === undefined) { return a === b; } const ac = collapse(a); const bc = collapse(b); return (ac.prim === bc.prim && ((ac.args === undefined && bc.args === undefined) || (ac.args !== undefined && bc.args !== undefined && ac.args.length === bc.args.length && ac.args.every((v, i) => deepEqual(v, bc.args?.[i] ?? {})))) && ((ac.annots === undefined && bc.annots === undefined) || (ac.annots !== undefined && bc.annots !== undefined && ac.annots.length === bc.annots.length && ac.annots.every((v, i) => v === bc.annots?.[i])))); } /** * @remarks Our current smart contract abstraction feature is currently in preview. Its API is not final, and it may not cover every use case (yet). We will greatly appreciate any feedback on this feature. */ class Schema { static isSchema(obj) { return obj && obj[schemaTypeSymbol] === true; } /** * @throws {@link InvalidRpcResponseError} */ static fromRPCResponse(val) { if (!val) { throw new errors_1.InvalidRpcResponseError(val, 'the RPC response is empty'); } if (!val.script) { throw new errors_1.InvalidRpcResponseError(val, 'the RPC response has no script'); } if (!Array.isArray(val.script.code)) { throw new errors_1.InvalidRpcResponseError(val, 'The response.script.code should be an array'); } let code = val.script.code; while (code.length === 1 && Array.isArray(code[0])) { code = code[0]; } const storage = code.find((x) => 'prim' in x && x.prim === 'storage'); if (!storage || !Array.isArray(storage.args)) { throw new errors_1.InvalidRpcResponseError(val, 'The response.script.code has an element of type {prim: "storage"}, but its args is not an array'); } return new Schema(storage.args[0]); } isExpressionExtended(val) { return 'prim' in val && Array.isArray(val.args); } constructor(val) { this.val = val; this[_a] = true; this.root = (0, createToken_1.createToken)(val, 0); if (this.root instanceof bigmap_1.BigMapToken) { this.bigMap = this.root; } else if (this.isExpressionExtended(val) && val.prim === 'pair') { const exp = val.args[0]; if (this.isExpressionExtended(exp) && exp.prim === 'big_map') { this.bigMap = new bigmap_1.BigMapToken(exp, 0, createToken_1.createToken); } } } removeTopLevelAnnotation(obj) { // PairToken and OrToken can have redundant top level annotation in their storage if (this.root instanceof pair_1.PairToken || this.root instanceof or_1.OrToken) { if (this.root.hasAnnotations() && typeof obj === 'object' && Object.keys(obj).length === 1) { return obj[Object.keys(obj)[0]]; } } return obj; } Execute(val, semantics) { const storage = this.root.Execute(val, semantics); return this.removeTopLevelAnnotation(storage); } /** * Validates that a value matches the schema type. * Performs type checking with special handling for BigMap, Ticket, and nested Map tokens. * * @param val - The value to validate against the schema * @returns Returns true if validation passes, false if validation fails */ Typecheck(val) { if (this.root instanceof bigmap_1.BigMapToken && Number.isInteger(Number(val))) { return true; } if (this.root instanceof ticket_1.TicketToken && val.ticketer && val.value && val.amount) { return true; } if (this.root instanceof ticket_deprecated_1.TicketDeprecatedToken && val.ticketer && val.value && val.amount) { return true; } if (this.root instanceof map_1.MapToken && this.root.ValueSchema instanceof bigmap_1.BigMapToken) { return true; } try { this.root.EncodeObject(val); return true; } catch (error) { return false; } } /** * @throws {@link InvalidBigMapSchemaError} * @throws {@link InvalidBigMapDiffError} */ ExecuteOnBigMapDiff(diff, semantics) { if (!this.bigMap) { throw new errors_1.InvalidBigMapSchemaError('Big map schema is undefined'); } if (!Array.isArray(diff)) { throw new errors_1.InvalidBigMapDiffError(`Big map diff must be an array, got: ${JSON.stringify(diff)}`, diff); } const eltFormat = diff.map(({ key, value }) => ({ args: [key, value] })); return this.bigMap.Execute(eltFormat, semantics); } /** * @throws {@link InvalidBigMapSchemaError} */ ExecuteOnBigMapValue(key, semantics) { if (!this.bigMap) { throw new errors_1.InvalidBigMapSchemaError('Big map schema is undefined'); } return this.bigMap.ValueSchema.Execute(key, semantics); } /** * @throws {@link InvalidBigMapSchemaError} * @throws {@link BigMapEncodingError} */ EncodeBigMapKey(key) { if (!this.bigMap) { throw new errors_1.InvalidBigMapSchemaError('Big map schema is undefined'); } try { return this.bigMap.KeySchema.ToBigMapKey(key); } catch (ex) { throw new errors_1.BigMapEncodingError('key', ex, this.bigMap.KeySchema, key); } } /** * @throws TokenValidationError * @throws {@link StorageEncodingError} */ Encode(value, semantics) { try { return this.root.EncodeObject(value, semantics); } catch (ex) { if (ex instanceof token_1.TokenValidationError) { throw ex; } throw new errors_1.StorageEncodingError('storage object', ex, this.root, value, semantics); } } /** * Produce a representation of the storage schema. * Note: Provide guidance on how to write the storage object for the origination operation with Taquito. */ generateSchema() { return this.removeTopLevelAnnotation(this.root.generateSchema()); } /** * Look up in top-level pairs of the storage to find a value matching the specified type * * @returns The first value found that match the type or `undefined` if no value is found * * @param storage storage to parse to find the value * @param valueType type of value to look for * */ FindFirstInTopLevelPair(storage, valueType) { return this.findValue(this.root['val'], storage, valueType); } // TODO check these type casts /** * @throws {@link MissingArgumentError} */ findValue(schema, storage, valueToFind) { if (deepEqual(valueToFind, schema)) { return storage; } if (Array.isArray(schema) || schema.prim === 'pair') { const sch = collapse(schema); const strg = collapse(storage, 'Pair'); if (sch.args === undefined || strg.args === undefined) { throw new errors_1.MissingArgumentError('Tokens have no arguments'); // unlikely } if (sch.args[0]) return ( // unsafe this.findValue(sch.args[0], strg.args[0], valueToFind) || this.findValue(sch.args[1], strg.args[1], valueToFind)); } } /** * Look up the schema to find any occurrence of a particular token. * * @returns an array of tokens of the specified kind or an empty array if no token was found * * @param tokenToFind string representing the prim property of the token to find * * @example * ``` * Useful to find all global constants in a script, an array of GlobalConstantToken is returned: * * const schema = new Schema(script); * const allGlobalConstantTokens = schema.findToken('constant'); * ``` * */ findToken(tokenToFind) { const tokens = []; return this.root.findAndReturnTokens(tokenToFind, tokens); } } exports.Schema = Schema; _a = schemaTypeSymbol;