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
JavaScript
"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;