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.

202 lines (201 loc) 9.43 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; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TracingInternal = void 0; const everscale_inpage_provider_1 = require("everscale-inpage-provider"); const console_abi_1 = require("../../console.abi"); const constants_1 = require("./constants"); const utils_1 = require("./utils"); const trace_1 = require("./trace/trace"); const lodash_1 = __importStar(require("lodash")); const logger_1 = require("../logger"); const viewTracingTree_1 = require("./viewTraceTree/viewTracingTree"); const utils_2 = require("../../utils"); const nekoton_wasm_1 = require("nekoton-wasm"); class TracingInternal { ever; factory; tracingTransport; network; labelsMap = new Map(); savedContracts = new Map(); consoleContract; _allowedCodes = { ...(0, utils_1.getDefaultAllowedCodes)(), contracts: {}, }; setContractLabels = (contracts) => { contracts.forEach(({ address, label }) => this.labelsMap.set((0, utils_1.extractStringAddress)(address), label)); }; constructor(ever, factory, tracingTransport, network) { this.ever = ever; this.factory = factory; this.tracingTransport = tracingTransport; this.network = network; this.consoleContract = new ever.Contract(console_abi_1.consoleAbi, new everscale_inpage_provider_1.Address(constants_1.CONSOLE_ADDRESS)); } saveContract = (address, contract) => { const stringAddress = (0, utils_1.extractStringAddress)(address); this.savedContracts.set(stringAddress, contract); }; getSavedContract = (address) => { const stringAddress = (0, utils_1.extractStringAddress)(address); return this.savedContracts.get(stringAddress); }; get allowedCodes() { return this._allowedCodes; } setAllowedCodes(allowedCodes) { if (allowedCodes.action) { this._allowedCodes.action.push(...allowedCodes.action); } if (allowedCodes.compute) { this._allowedCodes.compute.push(...allowedCodes.compute); } } setAllowedCodesForAddress(address, allowedCodes) { const stringAddress = address.toString(); if (!this._allowedCodes.contracts?.[stringAddress]) { this._allowedCodes.contracts[stringAddress] = (0, utils_1.getDefaultAllowedCodes)(); } if (allowedCodes.compute) { (this._allowedCodes.contracts[stringAddress].compute || []).push(...allowedCodes.compute); } if (allowedCodes.action) { (this._allowedCodes.contracts[stringAddress].action || []).push(...allowedCodes.action); } } removeAllowedCodesForAddress(address, codesToRemove) { const stringAddress = address.toString(); if (codesToRemove.compute) { this._allowedCodes.contracts[stringAddress].compute = (0, lodash_1.difference)(this._allowedCodes.contracts[stringAddress]?.compute || [], codesToRemove.compute); } if (codesToRemove.action) { this._allowedCodes.contracts[stringAddress].action = (0, lodash_1.difference)(this._allowedCodes.contracts[stringAddress]?.action || [], codesToRemove.action); } } removeAllowedCodes(codesToRemove) { if (codesToRemove.compute) { this._allowedCodes.compute = (0, lodash_1.difference)(this._allowedCodes.compute || [], codesToRemove.compute); } if (codesToRemove.action) { this._allowedCodes.action = (0, lodash_1.difference)(this._allowedCodes.action || [], codesToRemove.action); } } popKey = (obj, key) => { const value = obj[key]; delete obj[key]; return value; }; // input transactions are unordered buildMsgTree = async (transactions) => { // restructure transaction inside out for more convenient access in later processing const hashToMsg = {}; const msgs = transactions.map((tx) => { const extendedTx = (0, nekoton_wasm_1.decodeRawTransaction)(tx.boc); const inMsg = this.popKey(extendedTx, "inMessage"); const description = this.popKey(extendedTx, "description"); const outMsgs = this.popKey(extendedTx, "outMessages"); const msg = { ...inMsg, dstTransaction: { ...extendedTx, ...description }, outMessages: outMsgs, }; hashToMsg[msg.hash] = msg; // special move for extOut messages (events), because they don't have dstTransaction msg.outMessages.map(outMsg => { if (outMsg.msgType === "ExtOut") { // console.log(outMsg); hashToMsg[outMsg.hash] = { ...outMsg, dstTransaction: undefined, outMessages: [] }; } }); return msg; }); // recursively build message tree const buildTree = async (msgHash) => { const msg = hashToMsg[msgHash]; if (msg.dst === constants_1.CONSOLE_ADDRESS) { await this.printConsoleMsg(msg); } const outMessages = await Promise.all(msg.outMessages.map(async (outMsg) => buildTree(outMsg.hash))); return { ...msg, outMessages }; }; return await buildTree(msgs[0].hash); }; // allowed_codes example - {compute: [100, 50, 12], action: [11, 12], "ton_addr": {compute: [60], action: [2]}} async trace({ finalizedTx, allowedCodes, raise = true }) { // @ts-ignore const externalTx = (0, utils_2.extractTransactionFromParams)(finalizedTx.extTransaction); const msgTree = await this.buildMsgTree([externalTx, ...finalizedTx.transactions]); const accounts = (0, utils_1.extractAccountsFromMsgTree)(msgTree); const accountDataList = await this.tracingTransport.getAccountsData(accounts); const accountDataMap = accountDataList.reduce((acc, accountData) => ({ ...acc, [accountData.id]: accountData }), {}); const allowedCodesExtended = lodash_1.default.mergeWith(lodash_1.default.cloneDeep(this._allowedCodes), allowedCodes, (objValue, srcValue) => Array.isArray(objValue) ? objValue.concat(srcValue) : undefined); const traceTree = await this.buildTracingTree(msgTree, allowedCodesExtended, accountDataMap); const reverted = this.findRevertedBranch(lodash_1.default.cloneDeep(traceTree)); if (reverted && raise) { (0, utils_1.throwErrorInConsole)(reverted); } return new viewTracingTree_1.ViewTracingTree(traceTree, this.factory.getContractByCodeHash, accountDataList); } async printConsoleMsg(msg) { const decoded = await this.ever.rawApi.decodeEvent({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion body: msg.body, abi: JSON.stringify(console_abi_1.consoleAbi), event: "Log", }); logger_1.logger.printInfo(decoded && "_log" in decoded.data && decoded.data._log); } async buildTracingTree(msgTree, allowedCodes = { compute: [], action: [], contracts: { any: { compute: [], action: [] } } }, accountData) { const trace = new trace_1.Trace(this, msgTree, null, { accounts: accountData }); await trace.buildTree(allowedCodes); return trace; } // apply depth-first search on trace tree, return first found reverted branch findRevertedBranch(traceTree) { if (!traceTree.hasErrorInTree) { return; } return this.depthSearch(traceTree, 1, 0); } depthSearch(traceTree, totalActions, actionIdx) { if (traceTree.error && !traceTree.error.ignored) { // clean unnecessary structure traceTree.outTraces = []; return [{ totalActions, actionIdx: actionIdx, traceLog: traceTree }]; } for (const [index, trace] of traceTree.outTraces.entries()) { const actionsNum = traceTree.outTraces.length; const corruptedBranch = this.depthSearch(trace, actionsNum, index); if (corruptedBranch) { // clean unnecessary structure traceTree.outTraces = []; return [{ totalActions, actionIdx, traceLog: traceTree }].concat(corruptedBranch); } } } } exports.TracingInternal = TracingInternal;