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
text/typescript
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)
);
};