UNPKG

locklift

Version:

Node JS framework for working with Ever contracts. Inspired by Truffle and Hardhat. Helps you to build, test, run and maintain your smart contracts.

257 lines (256 loc) 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractAddress = exports.extractStringAddress = exports.isExistsInArr = exports.getDefaultAllowedCodes = exports.isT = exports.throwErrorInConsole = exports.throwTrace = exports.hexToValue = exports.convertForLogger = exports.convert = exports.extractAccountsFromMsgTree = void 0; const types_1 = require("./types"); const logger_1 = require("../logger"); const everscale_inpage_provider_1 = require("everscale-inpage-provider"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const path_1 = __importDefault(require("path")); const process = __importStar(require("process")); const chalk_1 = __importDefault(require("chalk")); const constants_1 = require("./constants"); const fs = require("fs"); const extractAccountsFromMsgTree = (msgTree) => { const extractAccounts = (msgTree) => { const accounts = msgTree.dst && msgTree.dst !== constants_1.CONSOLE_ADDRESS ? [new everscale_inpage_provider_1.Address(msgTree.dst)] : []; for (const outMsg of msgTree.outMessages) { accounts.push(...extractAccounts(outMsg)); } return accounts; }; return [...new Set(extractAccounts(msgTree))]; }; exports.extractAccountsFromMsgTree = extractAccountsFromMsgTree; const convert = (number, decimals = 9, precision = 4) => { return (number / 10 ** decimals).toPrecision(precision); }; exports.convert = convert; const convertForLogger = (amount) => new bignumber_js_1.default((0, exports.convert)(amount, 9, 8) || 0); exports.convertForLogger = convertForLogger; const hexToValue = (amount) => new bignumber_js_1.default((0, exports.convert)(amount, 9, 9) || 0); exports.hexToValue = hexToValue; const normalizeFilePath = (errorPosition) => { let errFilePath = errorPosition.filename; // contracts paths look like: "../contracts/ContractName.tsol" if (errFilePath.startsWith("../contracts/")) { errFilePath = path_1.default.resolve(process.cwd(), errFilePath.split("../")[1]); } // .code paths look like: "ContractName.code" if (errFilePath.endsWith(".code")) { errFilePath = path_1.default.resolve(process.cwd(), "build", errFilePath); } return errFilePath; }; const printErrorPositionSnippet = (trace, filename, errLine, offset) => { const errFile = fs.readFileSync(filename, "utf8"); const lines = errFile.split("\n"); const lastLineLen = `${errLine + offset}`.length; const { name, method } = getContractNameAndMethod(trace); logger_1.logger.printTracingLog("".padStart(lastLineLen - 1, " "), chalk_1.default.blueBright.bold("-->"), chalk_1.default.bold(`${name}.${method} (${path_1.default.basename(filename)}:${errLine})`)); logger_1.logger.printTracingLog("".padStart(lastLineLen, " "), chalk_1.default.blueBright.bold("|")); const linesToPrint = []; lines.map((line, i) => { if (i < errLine - offset - 1 || i >= errLine + offset) return; const lineNum = `${i + 1}`.padEnd(lastLineLen, " "); if (i === errLine - 1) { linesToPrint.push([chalk_1.default.redBright.bold(`${lineNum} |`), chalk_1.default.redBright(line)]); } else { linesToPrint.push([chalk_1.default.blueBright.bold(`${lineNum} |`), line]); } }); const firstNotEmpty = linesToPrint.findIndex(line => line[1].trim() !== ""); const lastNotEmpty = linesToPrint.length - linesToPrint.reverse().findIndex(line => line[1].trim() !== ""); linesToPrint .reverse() .slice(firstNotEmpty, lastNotEmpty) .map(line => { logger_1.logger.printTracingLog(...line); }); logger_1.logger.printTracingLog("".padStart(lastLineLen, " "), chalk_1.default.blueBright.bold("|")); }; const throwTrace = (trace) => { // const _trace = trace.transactionTrace!.map((trace) => JSON.stringify(trace)).join('\n'); // fs.writeFileSync('log.json', _trace); logger_1.logger.printTracingLog(chalk_1.default.redBright("-----------------------------------------------------------------")); // SKIPPED COMPUTE PHASE if (trace.error?.phase === "compute" && trace.error?.reason) { let errorDescription = trace.error?.reason; if (errorDescription === "NoState") { errorDescription = "NoState. Looks like you tried to call method of contract that doesn't exist"; } const errorMsg = `!!! Compute phase was skipped with reason: ${errorDescription} !!!`; logger_1.logger.printError(errorMsg); throw new Error(errorMsg); } let errorDescription = ""; if (trace.error?.phase === "action") { errorDescription = constants_1.ActionCodeHints[Number(trace.error.code)]; } if (trace.error?.phase === "compute") { errorDescription = constants_1.ComputeCodesHints[Number(trace.error.code)]; } // short common error description const mainErrorMsg = `!!! Reverted with ${trace.error?.code} error code on ${trace.error?.phase} phase !!!`; logger_1.logger.printError(mainErrorMsg); logger_1.logger.printError(errorDescription); logger_1.logger.printTracingLog(chalk_1.default.redBright("-----------------------------------------------------------------")); // no trace -> we cant detect line with error if (trace.transactionTrace === undefined) throw new Error(mainErrorMsg); const vmTraces = trace.transactionTrace; // no debug-map -> we cant detect line with error if (trace.contract.map === undefined) throw new Error(mainErrorMsg); const contract = trace.contract; const tx = trace.msg.dstTransaction; let errPosition; // COMPUTE PHASE ERROR if (tx.compute.status === "vm" && !tx.compute.success) { // last vm step is the error position const lastStep = vmTraces.pop(); errPosition = contract.map[lastStep.cmdCodeCellHash][lastStep.cmdCodeOffset]; if (errPosition === undefined) throw new Error(mainErrorMsg); } // ACTION PHASE ERROR if (tx.action?.success === false) { // catch all vm steps, where actions are produced const actionsSent = vmTraces.filter(t => t.cmdStr === "SENDRAWMSG" || t.cmdStr === "RAWRESERVE" || t.cmdStr === "SETCODE"); let failedAction = tx.action.resultArg; // too many actions, point to 256th action if (Number(tx.action.resultCode) === 33) failedAction = 255; const failedActionStep = actionsSent[failedAction]; errPosition = contract.map[failedActionStep.cmdCodeCellHash][failedActionStep.cmdCodeOffset]; if (errPosition === undefined) throw new Error(mainErrorMsg); } const errFilePath = normalizeFilePath(errPosition); const errLineNum = errPosition.line; const filename = path_1.default.basename(errFilePath); if (filename.endsWith(".tsol") || filename.endsWith(".sol")) { printErrorPositionSnippet(trace, errFilePath, errLineNum, 2); } throw new Error(mainErrorMsg); }; exports.throwTrace = throwTrace; const getContractNameAndMethod = (trace) => { let name = "undefinedContract"; if (trace.contract) { name = trace.contract.name; } let method = "undefinedMethod"; if (trace.decodedMsg?.method) { method = trace.decodedMsg.method; } else if (trace.type === types_1.TraceType.BOUNCE) { method = "onBounce"; } return { name, method }; }; const throwErrorInConsole = (revertedBranch) => { for (const { totalActions, actionIdx, traceLog } of revertedBranch) { const bounce = traceLog.msg.bounce; const { name, method } = getContractNameAndMethod(traceLog); let paramsStr = "()"; if (traceLog.decodedMsg) { if (Object.values(traceLog.decodedMsg.params || {}).length === 0) { paramsStr = "()"; } else { paramsStr = "(\n"; for (const [key, value] of Object.entries(traceLog.decodedMsg.params || {})) { paramsStr += ` ${key}: ${JSON.stringify(value, null, 4)}\n`; } paramsStr += ")"; } } logger_1.logger.printTracingLog("\t\t⬇\n\t\t⬇"); logger_1.logger.printTracingLog(`\t#${actionIdx + 1} action out of ${totalActions}`); // green tags logger_1.logger.printTracingLog(`Addr: \x1b[32m${traceLog.msg.dst}\x1b[0m`); logger_1.logger.printTracingLog(`MsgId: \x1b[32m${traceLog.msg.hash}\x1b[0m`); logger_1.logger.printTracingLog("-----------------------------------------------------------------"); if (traceLog.type === types_1.TraceType.BOUNCE) { logger_1.logger.printTracingLog("-> Bounced msg"); } if (traceLog.error && traceLog.error.ignored) { logger_1.logger.printTracingLog(`-> Ignored ${traceLog.error.code} code on ${traceLog.error.phase} phase`); } if (!traceLog.contract) { logger_1.logger.printTracingLog("-> Contract not deployed/Not recognized because build artifacts not provided"); } // bold tag logger_1.logger.printTracingLog(`\x1b[1m${name}.${method}\x1b[22m{value: ${(0, exports.convert)(Number(traceLog.msg.value))}, bounce: ${bounce}}${paramsStr}`); if (traceLog.msg.dstTransaction) { const tx = traceLog.msg.dstTransaction; if (tx.storage) { logger_1.logger.printTracingLog(`Storage fees: ${(0, exports.convert)(Number(tx.storage.storageFeesCollected))}`); } if (tx.compute) { const gasFees = tx.compute.status === "vm" ? tx.compute.gasFees : 0; logger_1.logger.printTracingLog(`Compute fees: ${(0, exports.convert)(Number(gasFees))}`); } if (tx.action) { logger_1.logger.printTracingLog(`Action fees: ${(0, exports.convert)(Number(tx.action.totalActionFees))}`); } logger_1.logger.printTracingLog(chalk_1.default.bold("Total fees:"), `${(0, exports.convert)(Number(tx.totalFees))}`); if (tx.compute.status === "vm") { const gasLimit = Number(tx.compute.gasLimit) === 0 ? 1000000 : Number(tx.compute.gasLimit); const percentage = ((Number(tx.compute.gasUsed) / gasLimit) * 100).toPrecision(2); logger_1.logger.printTracingLog(chalk_1.default.bold("Gas used:"), `${Number(tx.compute.gasUsed).toLocaleString()}/${gasLimit.toLocaleString()} (${percentage}%)`); } } if (traceLog.error && !traceLog.error.ignored) { (0, exports.throwTrace)(traceLog); } } }; exports.throwErrorInConsole = throwErrorInConsole; const isT = (p) => !!p; exports.isT = isT; const getDefaultAllowedCodes = () => ({ compute: [], action: [], }); exports.getDefaultAllowedCodes = getDefaultAllowedCodes; const isExistsInArr = (srcArr, isExist) => { return srcArr.some(item => item === isExist); }; exports.isExistsInArr = isExistsInArr; const extractStringAddress = (contract) => typeof contract === "string" ? contract : contract instanceof everscale_inpage_provider_1.Address ? contract.toString() : contract.address.toString(); exports.extractStringAddress = extractStringAddress; const extractAddress = (contract) => new everscale_inpage_provider_1.Address((0, exports.extractStringAddress)(contract)); exports.extractAddress = extractAddress;