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.

210 lines (184 loc) 6.88 kB
import { AccountData, Addressable, BalanceChangeInfoStorage, ErrorStore, MsgError, TraceType, ViewTrace, ViewTraceTree, ViewTraceTreeWithTotalFee, } from "../types"; import _ from "lodash"; import { AbiEventName, Address, Contract, DecodedEventWithTransaction } from "everscale-inpage-provider"; import { extractStringAddress, isT } from "../utils"; import { ContractWithArtifacts } from "../../../types"; import { applyTotalFees, calculateTotalFees, getBalanceChangingInfo, getBalanceDiff, getErrorsInfo, isDesiredMethod, printer, PrinterConfig, } from "./utils"; import { Tokens } from "./tokens"; import { pipe } from "rxjs"; export type NameAndType<T extends string = string> = { type: TraceType; name: T; contract?: Addressable }; type EventNames<Abi> = DecodedEventWithTransaction<Abi, AbiEventName<Abi>>["event"]; type EventsNamesInner<T extends Contract<any>> = EventNames<T extends Contract<infer f> ? f : never>; //@ts-ignore type MethodParams<C extends Contract<any>, N extends keyof C["methods"]> = Parameters<C["methods"][N]>[0]; type EventParams<Abi, N extends string> = Extract< DecodedEventWithTransaction<Abi, AbiEventName<Abi>>, { event: N } >["data"]; export class ViewTracingTree { readonly viewTraceTree: ViewTraceTreeWithTotalFee; readonly tokens: Tokens; balanceChangeInfo: BalanceChangeInfoStorage; msgErrorsStore: ErrorStore; constructor( viewTraceTree: ViewTraceTree, private readonly contractGetter: ( codeHash: string | undefined, address: Address, ) => ContractWithArtifacts<any> | undefined, private readonly accounts: AccountData[], ) { this.viewTraceTree = applyTotalFees(_.cloneDeep(viewTraceTree)); this.balanceChangeInfo = pipe(getBalanceChangingInfo, getBalanceDiff)(this.viewTraceTree); this.msgErrorsStore = getErrorsInfo(this.viewTraceTree); this.tokens = new Tokens(this.viewTraceTree); } getErrorsByContract = <T extends Contract<any> | Address | string>(contract: T): Array<MsgError> => { return this.msgErrorsStore[extractStringAddress(contract)]; }; getAllErrors = () => Object.entries(this.msgErrorsStore).flatMap(([key, errors]) => errors.map(error => ({ contract: key, ...error }))); getBalanceDiff = <T extends Contract<any> | Address | string>( contracts: T[] | T, ): T extends T[] ? Record<string, string> : string => { if (Array.isArray(contracts)) { return contracts.reduce((acc, contract) => { const address = extractStringAddress(contract); return { ...acc, [address as string]: this.balanceChangeInfo[address.toString()]?.balanceDiff.toString() }; }, {} as Record<string, string>) as T extends T[] ? Record<string, string> : string; } const address = extractStringAddress(contracts); return this.balanceChangeInfo[address].balanceDiff.toString() as T extends T[] ? Record<string, string> : string; }; findCallsForContract = <C extends Contract<any>, N extends keyof C["methods"] & string>({ contract, name, }: { contract: C } & { name: N; }) => { return this.findForContract({ contract, name }) .map(el => el?.params) .filter(isT); }; findEventsForContract = < C extends Contract<any>, Abi extends C extends Contract<infer f> ? f : never, N extends EventNames<Abi>, >({ contract, name, }: { contract: C } & { name: N }) => this.findForContract({ name, contract }) .map(el => el?.params) .filter(isT); findForContract = < C extends Contract<any>, N extends (keyof C["methods"] & string) | EventsNamesInner<C>, Abi extends C extends Contract<infer f> ? f : never, E extends N extends keyof C["methods"] & string ? MethodParams<C, N> : EventParams<Abi, N>, >({ contract, name, }: { contract: C } & { name: N }) => { if (name in contract.methodsAbi) { return this.findByType<N, MethodParams<C, N>>({ name, type: TraceType.FUNCTION_CALL, contract }); } return this.findByType<N, E>({ name, type: TraceType.EVENT, contract }); }; findByType = <M extends string, P>(params: NameAndType): Array<ViewTrace<M, P>["decodedMsg"]> => this._findByType<M, P>(params, this.viewTraceTree).map(el => el.decodedMsg); findByTypeWithFullData = <M extends string, P>(params: NameAndType) => { return this._findByType<M, P>(params, this.viewTraceTree); }; private _findByType = <M extends string, P>( { type, name, contract }: NameAndType, tree: ViewTraceTree, isRoot = true, ): Array<ViewTrace<M, P>> => { const matchedMethods: Array<ViewTrace<M, P>> = []; if (isRoot && isDesiredMethod({ type, name, contract }, tree)) { matchedMethods.push(tree as any); } for (const trace of tree.outTraces) { if (isDesiredMethod({ type, name, contract }, trace)) { matchedMethods.push(trace as any); } if (trace.outTraces.length > 0) { matchedMethods.push(...this._findByType<M, P>({ name, type, contract }, trace, false)); } } return matchedMethods; }; totalGasUsed = () => calculateTotalFees(this.viewTraceTree).toNumber(); beautyPrint = async (printerConfig?: PrinterConfig): Promise<void> => { const contracts = this.accounts.map(({ codeHash, id }) => this.contractGetter(codeHash, new Address(id))); console.log( printer( this.viewTraceTree, { contracts, }, printerConfig, ) + "\n" + this._beautyPrint(this.viewTraceTree, 0, contracts, printerConfig), ); }; private _beautyPrint = ( viewTrace: ViewTraceTreeWithTotalFee, offset: number, contracts: Array<ContractWithArtifacts | undefined>, printerConfig?: PrinterConfig, ): string => { let traces = ""; for (const viewTraceInt of viewTrace.outTraces) { traces = traces + `${Array(offset).fill(" ").join("")} ${printer( viewTraceInt, { contracts, }, printerConfig, )}\n${this._beautyPrint(viewTraceInt, offset + 1, contracts, printerConfig)}`; } return traces; }; } // eslint-disable-next-line @typescript-eslint/no-unused-vars const extractAllAddresses = (viewTrace: ViewTraceTree): Array<Address> => { const addresses: Array<Address> = extractAddressFromObject(viewTrace.decodedMsg || {}); return viewTrace.outTraces.reduce((acc, next) => { return [...acc, ...extractAllAddresses(next)]; }, addresses); }; const extractAddressFromObject = (obj: Record<any, any>): Array<Address> => { return Object.values(obj).reduce((acc, value) => { if (value instanceof Address) { return [...acc, value.toString()]; } if (value instanceof Object) { return [...acc, ...extractAddressFromObject(value)]; } return acc; }, [] as Array<Address>); };