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