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.

174 lines (173 loc) 7.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Trace = void 0; const constants_1 = require("../constants"); const types_1 = require("../types"); const everscale_inpage_provider_1 = require("everscale-inpage-provider"); const utils_1 = require("./utils"); class Trace { tracing; msg; srcTrace; context; outTraces = []; error = null; transactionTrace = undefined; type = null; contract; decodedMsg = undefined; hasErrorInTree = false; constructor(tracing, msg, srcTrace, context) { this.tracing = tracing; this.msg = msg; this.srcTrace = srcTrace; this.context = context; } async buildTree(allowedCodes = { compute: [], action: [], contracts: { any: { compute: [], action: [] } } }) { this.setMsgType(); const { codeHash, address } = (0, utils_1.contractInformation)({ msg: this.msg, type: this.type, ctx: this.context }); const contract = this.tracing.getSavedContract(address) || this.tracing.factory.getContractByCodeHashOrDefault(codeHash || "", new everscale_inpage_provider_1.Address(address)); this.checkForErrors(allowedCodes); if (this.error && !this.error.ignored && this.msg.dstTransaction) { this.transactionTrace = this.tracing.network.getTxTrace(this.msg.dstTransaction.hash)?.parsed; } await this.decode(contract); for (const msg of this.msg.outMessages) { const trace = new Trace(this.tracing, msg, this, this.context); await trace.buildTree(allowedCodes); if (trace.hasErrorInTree) { this.hasErrorInTree = true; } this.outTraces.push(trace); } } // allowed_codes - {compute: [100, 50, 12], action: [11, 12]} checkForErrors(allowedCodes = { compute: [], action: [], contracts: { any: { compute: [], action: [] } } }) { const tx = this.msg.dstTransaction; if (this.msg.dst === constants_1.CONSOLE_ADDRESS) { return; } let skipComputeCheck = false; if (tx && tx.compute.status === "vm" && tx.compute.success) { skipComputeCheck = true; } let skipActionCheck = false; if (tx && tx.action && tx.action.success) { skipActionCheck = true; } // error occured during compute phase if (!skipComputeCheck && tx) { if (tx.compute.status === "skipped") { this.error = { phase: "compute", code: null, reason: tx.compute.reason }; } else { this.error = { phase: "compute", code: tx.compute.exitCode, reason: undefined }; } // we didn't expect this error, save error if ((0, utils_1.isErrorExistsInAllowedArr)(allowedCodes.compute, this.error.code) || (0, utils_1.isErrorExistsInAllowedArr)(allowedCodes.contracts?.[this.msg.dst]?.compute, this.error.code)) { this.error.ignored = true; } } else if (!skipActionCheck && tx && tx.action && tx.action.resultCode !== 0) { this.error = { phase: "action", code: tx.action.resultCode, reason: undefined }; // we didn't expect this error, save error if ((0, utils_1.isErrorExistsInAllowedArr)(allowedCodes.action, tx.action.resultCode) || (0, utils_1.isErrorExistsInAllowedArr)(allowedCodes.contracts?.[this.msg.dst]?.action, tx.action.resultCode)) { this.error.ignored = true; } } if (this.error && !this.error.ignored) { this.hasErrorInTree = true; } } async decodeMsg(contract = null) { if (contract === null) { contract = this.contract; } if (this.msg.dst === constants_1.CONSOLE_ADDRESS) { return; } if (this.type === types_1.TraceType.TRANSFER || this.type === types_1.TraceType.BOUNCE) { return; } if (this.type === types_1.TraceType.FUNCTION_CALL && this.srcTrace) { // this is responsible callback with answerId = 0, we cant decode it, however contract doesnt need it too // TODO: check // @ts-ignore if (this.srcTrace.decodedMsg && this.srcTrace.decodedMsg.value?.answerId === "0") { return; } } // function call, but we dont have contract here => we cant decode msg if (this.type === types_1.TraceType.FUNCTION_CALL && !contract) { return; } // 60 error on compute phase - wrong function id. We cant decode this msg with contract abi if (this.error && this.error.phase === "compute" && this.error.code === 60) { return; } if (!contract) { return; } return await (0, utils_1.decoder)({ msgBody: this.msg.body, msgType: this.msg.msgType, contract, initialType: this.type, }); } async decode(contract) { this.contract = contract; const decoded = await this.decodeMsg(contract); if (decoded) { this.type = decoded.finalType; this.decodedMsg = decoded.decoded; } } setMsgType() { switch (this.msg.msgType) { // internal - deploy or function call or bound or transfer case "IntMsg": // code hash is presented, deploy if (this.msg.init?.codeHash !== undefined) { this.type = types_1.TraceType.DEPLOY; // bounced msg } else if (this.msg.bounced) { this.type = types_1.TraceType.BOUNCE; // empty body, just transfer } else if (this.msg.body === undefined) { this.type = types_1.TraceType.TRANSFER; } else { this.type = types_1.TraceType.FUNCTION_CALL; } return; // extIn - deploy or function call case "ExtIn": if (this.msg.init?.codeHash !== undefined) { this.type = types_1.TraceType.DEPLOY; } else { this.type = types_1.TraceType.FUNCTION_CALL; } return; // extOut - event or return case "ExtOut": // if this msg was produced by extIn msg, this can be return or event if (this.srcTrace !== null && this.srcTrace.msg.msgType === "ExtIn") { this.type = types_1.TraceType.EVENT_OR_FUNCTION_RETURN; } else { this.type = types_1.TraceType.EVENT; } return; default: return; } } } exports.Trace = Trace;