UNPKG

@mavrykdynamics/taquito-michel-codec

Version:

Michelson parser/validator/formatter

833 lines (832 loc) 27.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertDataListIfAny = exports.isInstruction = exports.isMichelsonType = exports.isMichelsonCode = exports.isMichelsonData = exports.isMichelsonScript = exports.assertMichelsonContract = exports.assertMichelsonData = exports.assertMichelsonType = exports.assertViewNameValid = exports.assertMichelsonBigMapStorableType = exports.assertMichelsonPassableType = exports.assertMichelsonStorableType = exports.assertMichelsonPushableType = exports.assertMichelsonPackableType = exports.assertMichelsonComparableType = exports.assertMichelsonInstruction = exports.MichelsonValidationError = exports.instructionIDs = void 0; const utils_1 = require("./utils"); // Michelson validator const maxViewNameLength = 31; const noArgInstructionIDs = { ABS: true, ADD: true, ADDRESS: true, AMOUNT: true, AND: true, APPLY: true, BALANCE: true, BLAKE2B: true, CAR: true, CDR: true, CHAIN_ID: true, CHECK_SIGNATURE: true, COMPARE: true, CONCAT: true, CONS: true, EDIV: true, EQ: true, EXEC: true, FAILWITH: true, GE: true, GET_AND_UPDATE: true, GT: true, HASH_KEY: true, IMPLICIT_ACCOUNT: true, INT: true, ISNAT: true, JOIN_TICKETS: true, KECCAK: true, LE: true, LEVEL: true, LSL: true, LSR: true, LT: true, MEM: true, MUL: true, NEG: true, NEQ: true, NEVER: true, NOT: true, NOW: true, OR: true, PACK: true, PAIRING_CHECK: true, READ_TICKET: true, SAPLING_VERIFY_UPDATE: true, SELF: true, SELF_ADDRESS: true, SENDER: true, SET_DELEGATE: true, SHA256: true, SHA3: true, SHA512: true, SIZE: true, SLICE: true, SOME: true, SOURCE: true, SPLIT_TICKET: true, SUB: true, SWAP: true, TICKET: true, TICKET_DEPRECATED: true, TOTAL_VOTING_POWER: true, TRANSFER_TOKENS: true, UNIT: true, VOTING_POWER: true, XOR: true, RENAME: true, OPEN_CHEST: true, SUB_MUMAV: true, MIN_BLOCK_TIME: true, BYTES: true, NAT: true, }; exports.instructionIDs = Object.assign({}, noArgInstructionIDs, { CONTRACT: true, CREATE_CONTRACT: true, DIG: true, DIP: true, DROP: true, DUG: true, DUP: true, EMIT: true, EMPTY_BIG_MAP: true, EMPTY_MAP: true, EMPTY_SET: true, GET: true, IF: true, IF_CONS: true, IF_LEFT: true, IF_NONE: true, ITER: true, LAMBDA: true, LAMBDA_REC: true, LEFT: true, LOOP: true, LOOP_LEFT: true, MAP: true, NIL: true, NONE: true, PAIR: true, PUSH: true, RIGHT: true, SAPLING_EMPTY_STATE: true, UNPACK: true, UNPAIR: true, UPDATE: true, CAST: true, VIEW: true, }); const simpleComparableTypeIDs = { unit: true, never: true, bool: true, int: true, nat: true, string: true, chain_id: true, bytes: true, mumav: true, key_hash: true, key: true, signature: true, timestamp: true, address: true, tx_rollup_l2_address: true, }; const typeIDs = Object.assign({}, simpleComparableTypeIDs, { or: true, pair: true, set: true, big_map: true, contract: true, lambda: true, list: true, map: true, operation: true, option: true, bls12_381_g1: true, bls12_381_g2: true, bls12_381_fr: true, sapling_transaction: true, sapling_transaction_deprecated: true, sapling_state: true, ticket: true, chest_key: true, chest: true, }); class MichelsonValidationError extends utils_1.MichelsonError { /** * @param val Value of a node caused the error * @param message An error message */ constructor(val, message) { super(val, message); this.val = val; this.message = message; this.name = 'MichelsonValidationError'; } } exports.MichelsonValidationError = MichelsonValidationError; function isPrim(ex) { return 'prim' in ex; } function isPrimOrSeq(ex) { return Array.isArray(ex) || 'prim' in ex; } function assertPrim(ex) { if (isPrim(ex)) { return true; } throw new MichelsonValidationError(ex, 'prim expression expected'); } function assertSeq(ex) { if (Array.isArray(ex)) { return true; } throw new MichelsonValidationError(ex, 'sequence expression expected'); } function assertPrimOrSeq(ex) { if (isPrimOrSeq(ex)) { return true; } throw new MichelsonValidationError(ex, 'prim or sequence expression expected'); } function assertNatural(i) { if (i.int[0] === '-') { throw new MichelsonValidationError(i, 'natural number expected'); } } function assertIntLiteral(ex) { if ('int' in ex) { return true; } throw new MichelsonValidationError(ex, 'int literal expected'); } function assertStringLiteral(ex) { if ('string' in ex) { return true; } throw new MichelsonValidationError(ex, 'string literal expected'); } // usually an address function assertStringOrBytes(ex) { if ('string' in ex || 'bytes' in ex) { return true; } throw new MichelsonValidationError(ex, 'string or bytes literal expected'); } function assertArgs(ex, n) { var _a; if ((n === 0 && ex.args === undefined) || ((_a = ex.args) === null || _a === void 0 ? void 0 : _a.length) === n) { return true; } throw new MichelsonValidationError(ex, `${n} arguments expected`); } /** * Checks if the node is a valid Michelson code (sequence of instructions). * This is a type guard function which either returns true of throws an exception. * @param ex An AST node */ function assertMichelsonInstruction(ex) { var _a, _b; if (Array.isArray(ex)) { for (const n of ex) { if (!Array.isArray(n) && !isPrim(n)) { throw new MichelsonValidationError(ex, 'sequence or prim expected'); } assertMichelsonInstruction(n); } return true; } if (assertPrim(ex)) { if (Object.prototype.hasOwnProperty.call(noArgInstructionIDs, ex.prim)) { assertArgs(ex, 0); return true; } switch (ex.prim) { case 'DROP': case 'PAIR': case 'UNPAIR': case 'DUP': case 'UPDATE': case 'GET': if (ex.args !== undefined && assertArgs(ex, 1)) { /* istanbul ignore else */ if (assertIntLiteral(ex.args[0])) { assertNatural(ex.args[0]); } } break; case 'DIG': case 'DUG': case 'SAPLING_EMPTY_STATE': /* istanbul ignore else */ if (assertArgs(ex, 1)) { /* istanbul ignore else */ if (assertIntLiteral(ex.args[0])) { assertNatural(ex.args[0]); } } break; case 'NONE': case 'LEFT': case 'RIGHT': case 'NIL': case 'CAST': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonType(ex.args[0]); } break; case 'UNPACK': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonPackableType(ex.args[0]); } break; case 'CONTRACT': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonPassableType(ex.args[0]); } break; case 'IF_NONE': case 'IF_LEFT': case 'IF_CONS': case 'IF': /* istanbul ignore else */ if (assertArgs(ex, 2)) { /* istanbul ignore else */ if (assertSeq(ex.args[0])) { assertMichelsonInstruction(ex.args[0]); } /* istanbul ignore else */ if (assertSeq(ex.args[1])) { assertMichelsonInstruction(ex.args[1]); } } break; case 'MAP': case 'ITER': case 'LOOP': case 'LOOP_LEFT': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonInstruction(ex.args[0]); } break; case 'CREATE_CONTRACT': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonContract(ex.args[0]); } break; case 'DIP': if (((_a = ex.args) === null || _a === void 0 ? void 0 : _a.length) === 2) { /* istanbul ignore else */ if (assertIntLiteral(ex.args[0])) { assertNatural(ex.args[0]); } /* istanbul ignore else */ if (assertSeq(ex.args[1])) { assertMichelsonInstruction(ex.args[1]); } } else if (((_b = ex.args) === null || _b === void 0 ? void 0 : _b.length) === 1) { /* istanbul ignore else */ if (assertSeq(ex.args[0])) { assertMichelsonInstruction(ex.args[0]); } } else { throw new MichelsonValidationError(ex, '1 or 2 arguments expected'); } break; case 'PUSH': /* istanbul ignore else */ if (assertArgs(ex, 2)) { assertMichelsonPushableType(ex.args[0]); assertMichelsonData(ex.args[1]); } break; case 'EMPTY_SET': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonComparableType(ex.args[0]); } break; case 'EMPTY_MAP': /* istanbul ignore else */ if (assertArgs(ex, 2)) { assertMichelsonComparableType(ex.args[0]); assertMichelsonType(ex.args[1]); } break; case 'EMPTY_BIG_MAP': /* istanbul ignore else */ if (assertArgs(ex, 2)) { assertMichelsonComparableType(ex.args[0]); assertMichelsonBigMapStorableType(ex.args[1]); } break; case 'LAMBDA_REC': case 'LAMBDA': /* istanbul ignore else */ if (assertArgs(ex, 3)) { assertMichelsonType(ex.args[0]); assertMichelsonType(ex.args[1]); /* istanbul ignore else */ if (assertSeq(ex.args[2])) { assertMichelsonInstruction(ex.args[2]); } } break; case 'VIEW': /* istanbul ignore else */ if (assertArgs(ex, 2)) { if (assertStringLiteral(ex.args[0])) { assertViewNameValid(ex.args[0]); } if (assertMichelsonType(ex.args[1])) { assertMichelsonPushableType(ex.args[1]); } } break; case 'EMIT': if (ex.args && ex.args.length > 0) { assertArgs(ex, 1); } else { assertArgs(ex, 0); } break; default: throw new MichelsonValidationError(ex, 'instruction expected'); } } return true; } exports.assertMichelsonInstruction = assertMichelsonInstruction; function assertMichelsonComparableType(ex) { /* istanbul ignore else */ if (assertPrimOrSeq(ex)) { if (Array.isArray(ex) || ex.prim === 'pair' || ex.prim === 'or' || ex.prim === 'option') { traverseType(ex, (ex) => assertMichelsonComparableType(ex)); } else if (!Object.prototype.hasOwnProperty.call(simpleComparableTypeIDs, ex.prim)) { throw new MichelsonValidationError(ex, `${ex.prim}: type is not comparable`); } } return true; } exports.assertMichelsonComparableType = assertMichelsonComparableType; function assertMichelsonPackableType(ex) { /* istanbul ignore else */ if (assertPrimOrSeq(ex)) { if (isPrim(ex)) { if (!Object.prototype.hasOwnProperty.call(typeIDs, ex.prim) || ex.prim === 'big_map' || ex.prim === 'operation' || ex.prim === 'sapling_state' || ex.prim === 'ticket') { throw new MichelsonValidationError(ex, `${ex.prim}: type can't be used inside PACK/UNPACK instructions`); } traverseType(ex, (ex) => assertMichelsonPackableType(ex)); } } return true; } exports.assertMichelsonPackableType = assertMichelsonPackableType; function assertMichelsonPushableType(ex) { /* istanbul ignore else */ if (assertPrimOrSeq(ex)) { if (isPrim(ex)) { if (!Object.prototype.hasOwnProperty.call(typeIDs, ex.prim) || ex.prim === 'big_map' || ex.prim === 'operation' || ex.prim === 'sapling_state' || ex.prim === 'ticket' || ex.prim === 'contract') { throw new MichelsonValidationError(ex, `${ex.prim}: type can't be pushed`); } traverseType(ex, (ex) => assertMichelsonPushableType(ex)); } } return true; } exports.assertMichelsonPushableType = assertMichelsonPushableType; function assertMichelsonStorableType(ex) { /* istanbul ignore else */ if (assertPrimOrSeq(ex)) { if (isPrim(ex)) { if (!Object.prototype.hasOwnProperty.call(typeIDs, ex.prim) || ex.prim === 'operation' || ex.prim === 'contract') { throw new MichelsonValidationError(ex, `${ex.prim}: type can't be used as part of a storage`); } traverseType(ex, (ex) => assertMichelsonStorableType(ex)); } } return true; } exports.assertMichelsonStorableType = assertMichelsonStorableType; function assertMichelsonPassableType(ex) { /* istanbul ignore else */ if (assertPrimOrSeq(ex)) { if (isPrim(ex)) { if (!Object.prototype.hasOwnProperty.call(typeIDs, ex.prim) || ex.prim === 'operation') { throw new MichelsonValidationError(ex, `${ex.prim}: type can't be used as part of a parameter`); } traverseType(ex, (ex) => assertMichelsonPassableType(ex)); } } return true; } exports.assertMichelsonPassableType = assertMichelsonPassableType; function assertMichelsonBigMapStorableType(ex) { /* istanbul ignore else */ if (assertPrimOrSeq(ex)) { if (isPrim(ex)) { if (!Object.prototype.hasOwnProperty.call(typeIDs, ex.prim) || ex.prim === 'big_map' || ex.prim === 'operation' || ex.prim === 'sapling_state') { throw new MichelsonValidationError(ex, `${ex.prim}: type can't be used inside a big_map`); } traverseType(ex, (ex) => assertMichelsonBigMapStorableType(ex)); } } return true; } exports.assertMichelsonBigMapStorableType = assertMichelsonBigMapStorableType; const viewRe = new RegExp('^[a-zA-Z0-9_.%@]*$'); function assertViewNameValid(name) { if (name.string.length > maxViewNameLength) { throw new MichelsonValidationError(name, `view name too long: ${name.string}`); } if (!viewRe.test(name.string)) { throw new MichelsonValidationError(name, `invalid character(s) in view name: ${name.string}`); } } exports.assertViewNameValid = assertViewNameValid; /** * Checks if the node is a valid Michelson type expression. * This is a type guard function which either returns true of throws an exception. * @param ex An AST node */ function assertMichelsonType(ex) { /* istanbul ignore else */ if (assertPrimOrSeq(ex)) { if (isPrim(ex)) { if (!Object.prototype.hasOwnProperty.call(typeIDs, ex.prim)) { throw new MichelsonValidationError(ex, 'type expected'); } traverseType(ex, (ex) => assertMichelsonType(ex)); } } return true; } exports.assertMichelsonType = assertMichelsonType; function traverseType(ex, cb) { if (Array.isArray(ex) || ex.prim === 'pair') { const args = Array.isArray(ex) ? ex : ex.args; if (args === undefined || args.length < 2) { throw new MichelsonValidationError(ex, 'at least 2 arguments expected'); } args.forEach((a) => { if (assertPrimOrSeq(a)) { cb(a); } }); return true; } switch (ex.prim) { case 'option': case 'list': /* istanbul ignore else */ if (assertArgs(ex, 1) && assertPrimOrSeq(ex.args[0])) { cb(ex.args[0]); } break; case 'contract': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonPassableType(ex.args[0]); } break; case 'or': /* istanbul ignore else */ if (assertArgs(ex, 2) && assertPrimOrSeq(ex.args[0]) && assertPrimOrSeq(ex.args[1])) { cb(ex.args[0]); cb(ex.args[1]); } break; case 'lambda': /* istanbul ignore else */ if (assertArgs(ex, 2)) { assertMichelsonType(ex.args[0]); assertMichelsonType(ex.args[1]); } break; case 'set': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonComparableType(ex.args[0]); } break; case 'map': /* istanbul ignore else */ if (assertArgs(ex, 2) && assertPrimOrSeq(ex.args[0]) && assertPrimOrSeq(ex.args[1])) { assertMichelsonComparableType(ex.args[0]); cb(ex.args[1]); } break; case 'big_map': /* istanbul ignore else */ if (assertArgs(ex, 2) && assertPrimOrSeq(ex.args[0]) && assertPrimOrSeq(ex.args[1])) { assertMichelsonComparableType(ex.args[0]); assertMichelsonBigMapStorableType(ex.args[1]); cb(ex.args[1]); } break; case 'ticket': /* istanbul ignore else */ if (assertArgs(ex, 1) && assertPrimOrSeq(ex.args[0])) { assertMichelsonComparableType(ex.args[0]); } break; case 'sapling_state': case 'sapling_transaction': if (assertArgs(ex, 1)) { assertIntLiteral(ex.args[0]); } break; default: assertArgs(ex, 0); } return true; } /** * Checks if the node is a valid Michelson data literal such as `(Pair {Elt "0" 0} 0)`. * This is a type guard function which either returns true of throws an exception. * @param ex An AST node */ function assertMichelsonData(ex) { if ('int' in ex || 'string' in ex || 'bytes' in ex) { return true; } if (Array.isArray(ex)) { let mapElts = 0; for (const n of ex) { if (isPrim(n) && n.prim === 'Elt') { /* istanbul ignore else */ if (assertArgs(n, 2)) { assertMichelsonData(n.args[0]); assertMichelsonData(n.args[1]); } mapElts++; } else { assertMichelsonData(n); } } if (mapElts !== 0 && mapElts !== ex.length) { throw new MichelsonValidationError(ex, "data entries and map elements can't be intermixed"); } return true; } if (isPrim(ex)) { switch (ex.prim) { case 'Unit': case 'True': case 'False': case 'None': assertArgs(ex, 0); break; case 'Pair': /* istanbul ignore else */ if (ex.args === undefined || ex.args.length < 2) { throw new MichelsonValidationError(ex, 'at least 2 arguments expected'); } for (const a of ex.args) { assertMichelsonData(a); } break; case 'Left': case 'Right': case 'Some': /* istanbul ignore else */ if (assertArgs(ex, 1)) { assertMichelsonData(ex.args[0]); } break; case 'Lambda_rec': if (ex.args) { assertMichelsonInstruction(ex.args); } break; case 'Ticket': if (assertArgs(ex, 4)) { assertStringOrBytes(ex.args[0]); assertMichelsonType(ex.args[1]); assertMichelsonData(ex.args[2]); assertIntLiteral(ex.args[3]); } break; default: if (Object.prototype.hasOwnProperty.call(exports.instructionIDs, ex.prim)) { assertMichelsonInstruction(ex); } else { throw new MichelsonValidationError(ex, 'data entry or instruction expected'); } } } else { throw new MichelsonValidationError(ex, 'data entry expected'); } return true; } exports.assertMichelsonData = assertMichelsonData; /** * Checks if the node is a valid Michelson smart contract source containing all required and valid properties such as `parameter`, `storage` and `code`. * This is a type guard function which either returns true of throws an exception. * @param ex An AST node */ function assertMichelsonContract(ex) { /* istanbul ignore else */ if (assertSeq(ex)) { const toplevelSec = {}; const views = {}; for (const sec of ex) { if (assertPrim(sec)) { if (sec.prim !== 'view') { if (sec.prim in toplevelSec) { throw new MichelsonValidationError(ex, `duplicate contract section: ${sec.prim}`); } toplevelSec[sec.prim] = true; } /* istanbul ignore else */ switch (sec.prim) { case 'code': if (assertArgs(sec, 1)) { /* istanbul ignore else */ if (assertSeq(sec.args[0])) { assertMichelsonInstruction(sec.args[0]); } } break; case 'parameter': if (assertArgs(sec, 1)) { assertMichelsonPassableType(sec.args[0]); } if (sec.annots) { throw new MichelsonValidationError(sec, 'Annotation must be part of the parameter type'); } break; case 'storage': if (assertArgs(sec, 1)) { assertMichelsonStorableType(sec.args[0]); } break; case 'view': if (assertArgs(sec, 4)) { if (assertStringLiteral(sec.args[0])) { const name = sec.args[0]; if (name.string in views) { throw new MichelsonValidationError(ex, `duplicate view name: ${name.string}`); } views[name.string] = true; assertViewNameValid(name); } assertMichelsonPushableType(sec.args[1]); assertMichelsonPushableType(sec.args[2]); if (assertSeq(sec.args[3])) { assertMichelsonInstruction(sec.args[3]); } } break; default: throw new MichelsonValidationError(ex, `unexpected contract section: ${sec.prim}`); } } } } return true; } exports.assertMichelsonContract = assertMichelsonContract; /** * Checks if the node is a valid Michelson smart contract source containing all required and valid properties such as `parameter`, `storage` and `code`. * @param ex An AST node */ function isMichelsonScript(ex) { try { assertMichelsonContract(ex); return true; } catch (_a) { return false; } } exports.isMichelsonScript = isMichelsonScript; /** * Checks if the node is a valid Michelson data literal such as `(Pair {Elt "0" 0} 0)`. * @param ex An AST node */ function isMichelsonData(ex) { try { assertMichelsonData(ex); return true; } catch (_a) { return false; } } exports.isMichelsonData = isMichelsonData; /** * Checks if the node is a valid Michelson code (sequence of instructions). * @param ex An AST node */ function isMichelsonCode(ex) { try { assertMichelsonInstruction(ex); return true; } catch (_a) { return false; } } exports.isMichelsonCode = isMichelsonCode; /** * Checks if the node is a valid Michelson type expression. * @param ex An AST node */ function isMichelsonType(ex) { try { assertMichelsonType(ex); return true; } catch (_a) { return false; } } exports.isMichelsonType = isMichelsonType; function isInstruction(p) { return Object.prototype.hasOwnProperty.call(exports.instructionIDs, p.prim); } exports.isInstruction = isInstruction; function assertDataListIfAny(d) { if (!Array.isArray(d)) { return false; } for (const v of d) { if ('prim' in v) { if (isInstruction(v)) { throw new utils_1.MichelsonError(d, `Instruction outside of a lambda: ${JSON.stringify(d)}`); } else if (v.prim === 'Elt') { throw new utils_1.MichelsonError(d, `Elt item outside of a map literal: ${JSON.stringify(d)}`); } } } return true; } exports.assertDataListIfAny = assertDataListIfAny;