@mavrykdynamics/taquito-michel-codec
Version:
Michelson parser/validator/formatter
833 lines (832 loc) • 27.5 kB
JavaScript
;
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;