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.

159 lines (145 loc) 5.8 kB
import { BalanceChangeInfoStorage, MsgError, TraceType, ViewTraceTree, ViewTraceTreeWithTotalFee } from "../types"; import chalk from "chalk"; import { ContractWithArtifacts } from "../../../types"; import { convertForLogger, extractAddress } from "../utils"; import { extractFeeAndSentValueFromMessage, mapParams } from "./mappers"; import BigNumber from "bignumber.js"; import _ from "lodash"; import { NameAndType } from "./viewTracingTree"; export const mapType: Record<TraceType, string> = { [TraceType.BOUNCE]: "BOUNCE", [TraceType.DEPLOY]: "DEPLOY", [TraceType.EVENT]: "EVENT", [TraceType.EVENT_OR_FUNCTION_RETURN]: "EVENT_OR_RETURN", [TraceType.FUNCTION_CALL]: "CALL", [TraceType.FUNCTION_RETURN]: "RETURN", [TraceType.TRANSFER]: "TRANSFER", }; export const colors: Record<"contractName" | "methodName" | "paramsKey" | "error", (param?: string) => string> = { contractName: chalk.cyan, methodName: chalk.blueBright, paramsKey: chalk.magenta, error: chalk.red, }; export const applyTotalFees = (viewTrace: ViewTraceTree): ViewTraceTreeWithTotalFee => { return { ...viewTrace, ...extractFeeAndSentValueFromMessage(viewTrace), outTraces: viewTrace.outTraces.map(applyTotalFees), }; }; export const calculateTotalFees = (traceTree: ViewTraceTreeWithTotalFee): BigNumber => { return traceTree.totalFees.plus( traceTree.outTraces .map(internalTraceTree => calculateTotalFees(internalTraceTree)) .reduce((acc, next) => acc.plus(next), new BigNumber(0)), ); }; export type PrinterConfig = | { fullPrint: boolean; } | { printFullAddresses: boolean; } | { printFillParams: boolean }; export type PrinterProps = Pick< ViewTraceTreeWithTotalFee, "type" | "decodedMsg" | "msg" | "contract" | "totalFees" | "sentValue" | "value" | "balanceChange" | "error" >; export const printer = ( { type, decodedMsg, contract, totalFees, sentValue, value, balanceChange, error }: PrinterProps, { contracts }: { contracts: Array<ContractWithArtifacts | undefined> }, printerConfig: PrinterConfig = {} as PrinterConfig, ): string => { const valueParams = `{valueReceive: ${convertForLogger(value.toNumber())},valueSent: ${convertForLogger( sentValue.toNumber(), )}, rest: ${convertForLogger(Number(balanceChange.toFixed(10)))}${ balanceChange.isLessThan(0) ? chalk.red("⮯") : chalk.green("⮬") }, totalFees: ${convertForLogger(totalFees.toNumber())}}`; const contractAddress = contract.contract.address.toString(); const printAddress = "fullPrint" in printerConfig || "printFullAddresses" in printerConfig ? contractAddress : contractAddress.slice(0, 5) + "..." + contractAddress.slice(-5); const header = `${type && mapType[type]}${ error ? ` ERROR (phase: ${error.phase}, code: ${error.code})` : "" } ${colors.methodName(printAddress)} ${colors.contractName(contract.name)}.${colors.methodName(decodedMsg?.method)}${ type === TraceType.EVENT ? "" : valueParams }`; const printMsg = `${header}(${Object.entries( mapParams(decodedMsg?.params, contracts, "printFillParams" in printerConfig && printerConfig.printFillParams), ) .map(([key, value]) => `${colors.paramsKey(key)}=${JSON.stringify(value)}, `) .join("") .split(", ") .slice(0, -1) .join(", ")})`; return error ? colors.error(printMsg) : printMsg; }; export type BalanceChangingInfo = { totalReceive: BigNumber; totalSent: BigNumber; balanceDiff: BigNumber; }; type BalanceChangeInfo = Record<string, Omit<BalanceChangingInfo, "balanceDiff">>; export const getBalanceChangingInfo = ( viewTrace: ViewTraceTreeWithTotalFee, accumulator: BalanceChangeInfo = {}, ): BalanceChangeInfo => { const contractAddress = viewTrace.contract?.contract.address.toString(); if (!(viewTrace.contract?.contract?.address.toString() in accumulator)) { accumulator[contractAddress] = { totalReceive: new BigNumber(0), totalSent: new BigNumber(0), }; } const { totalSent, totalReceive } = accumulator[contractAddress]; accumulator[contractAddress] = { totalReceive: totalReceive.plus(viewTrace.msg.value || 0), totalSent: totalSent.plus(viewTrace.sentValue || 0).plus(viewTrace.totalFees), }; return { ...accumulator, ...viewTrace.outTraces.reduce((acc, next) => { return { ...acc, ...getBalanceChangingInfo(next, accumulator) }; }, {} as BalanceChangeInfo), }; }; export const getBalanceDiff = (balanceChangeInfo: BalanceChangeInfo): BalanceChangeInfoStorage => { return Object.entries(balanceChangeInfo).reduce((acc, [address, { totalSent, totalReceive }]) => { return { ...acc, [address]: { balanceDiff: totalReceive.minus(totalSent) } }; }, {} as BalanceChangeInfoStorage); }; type ErrorInfoStorage = Record<string, Array<MsgError>>; export const getErrorsInfo = ( viewTrace: ViewTraceTreeWithTotalFee, accumulator: ErrorInfoStorage = {}, ): ErrorInfoStorage => { if (viewTrace.error) { const newError = { code: viewTrace.error.code, phase: viewTrace.error.phase, trace: _(viewTrace).omit("outTraces").value(), }; const address = viewTrace.contract?.contract?.address.toString(); if (!(address in accumulator)) { accumulator[address] = []; } accumulator[address].push(newError); } return { ...accumulator, ...viewTrace.outTraces.reduce( (acc, internalTrace) => ({ ...acc, ...getErrorsInfo(internalTrace, accumulator) }), {}, ), }; }; export const isDesiredMethod = ({ name, type, contract }: NameAndType, trace: ViewTraceTree): boolean => { return ( type === trace.type && name === trace.decodedMsg?.method && (contract ? extractAddress(contract).equals(extractAddress(trace.contract.contract)) : true) ); };