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.

243 lines (224 loc) 8.29 kB
import fs from "fs"; import path from "path"; import commander from "commander"; import { ProviderRpcClient } from "everscale-inpage-provider"; import type { ConnectionData, Ed25519KeyPair } from "everscale-standalone-client"; import { ConnectionProperties, NETWORK_PRESETS } from "everscale-standalone-client/nodejs"; import { Giver } from "../factory"; import Joi from "joi"; import { MessageProperties } from "everscale-standalone-client/client"; import * as nt from "nekoton-wasm"; import semver from "semver/preload"; export enum ConfigState { EXTERNAL, INTERNAL, } export const LOCKLIFT_NETWORK_NAME = "locklift"; type LockliftNetworkName = typeof LOCKLIFT_NETWORK_NAME; export interface LockliftConfig<T extends ConfigState = ConfigState.EXTERNAL> { compiler: { includesPath?: string; externalContracts?: ExternalContracts; externalContractsArtifacts?: ExternalContracts; compilerParams?: Array<string>; } & ({ path: string } | { version: string }); linker?: | { path: string; lib: string; } | { version: string }; networks: Networks<T>; mocha: Mocha.MochaOptions & { tsconfig?: string; }; } export type KeysConfig = { path?: string; phrase?: string; amount: number; }; export type ForkContractsConfig = Array< { abi: { path: string; }; } & ({ codeHash: string | { deriveAddress: string } } | { address: string }) >; export type ForkSource = { type: "live"; connection: ConnectionProperties } | { type: "block"; block: number }; export type Networks<T extends ConfigState = ConfigState.EXTERNAL> = Record<"local" | string, NetworkValue<T>> & { [key in LockliftNetworkName]: NetworkValue<T, LockliftNetworkName>; }; export interface NetworkValue<T extends ConfigState = ConfigState.EXTERNAL, P extends string = ""> { giver: T extends ConfigState.EXTERNAL ? P extends LockliftNetworkName ? GiverConfig | undefined : GiverConfig : GiverConfig; keys: T extends ConfigState.EXTERNAL ? KeysConfig : Required<KeysConfig>; connection: T extends ConfigState.EXTERNAL ? ConnectionProperties : ConnectionData; fork?: { source: ForkSource; contracts: ForkContractsConfig; }; blockchainConfig?: "EVER" | "TON" | { custom: string } | undefined; clientConfig?: { message?: MessageProperties; initInput?: any | Promise<any>; }; } export type ExternalContracts = Record<string, Array<string>>; export type GiverConfig = { address: string; giverFactory?: (ever: ProviderRpcClient, keyPair: Ed25519KeyPair, address: string) => Giver; } & ({ key: string } | { phrase: string; accountId: number }); export const JoiConfig = Joi.object<LockliftConfig>({ compiler: Joi.alternatives([ Joi.object({ includesPath: Joi.string().optional(), compilerParams: Joi.array().items(Joi.string()).optional(), externalContracts: Joi.object().pattern(Joi.string(), Joi.array().items(Joi.string())), externalContractsArtifacts: Joi.object().pattern(Joi.string(), Joi.array().items(Joi.string())), path: Joi.string(), }), Joi.object({ includesPath: Joi.string().optional(), compilerParams: Joi.array().items(Joi.string()).optional(), externalContracts: Joi.object().pattern(Joi.string(), Joi.array().items(Joi.string())), externalContractsArtifacts: Joi.object().pattern(Joi.string(), Joi.array().items(Joi.string())), version: Joi.string().required(), }), ]), linker: Joi.when("compiler.version", { is: Joi.custom((value, helpers) => { if (semver.lte(value, "0.71.0")) { return value; } return helpers.message({ custom: "DUMMY MSG" }); }), then: Joi.alternatives([ Joi.object({ path: Joi.string(), lib: Joi.string(), }), Joi.object({ version: Joi.string(), }), ]).required(), otherwise: Joi.any(), }), networks: Joi.object().pattern( Joi.string(), Joi.object({ giver: Joi.alternatives().conditional(Joi.object({ phrase: Joi.string().required() }).unknown(), { then: Joi.object({ address: Joi.string(), giverFactory: Joi.any().optional(), phrase: Joi.string(), accountId: Joi.number(), }), otherwise: Joi.object({ address: Joi.string(), giverFactory: Joi.any().optional(), key: Joi.string(), }), }), keys: Joi.object({ path: Joi.string().optional(), phrase: Joi.string().optional(), amount: Joi.number(), }), connection: Joi.alternatives([ ...Object.keys(NETWORK_PRESETS).map(el => el), Joi.object({ id: Joi.number().optional(), type: Joi.alternatives(["graphql", "jrpc", "proxy"]), group: Joi.string().optional(), data: Joi.alternatives().conditional("type", { is: "graphql", then: Joi.object({ endpoints: Joi.array().items(Joi.string()), local: Joi.boolean().optional(), latencyDetectionInterval: Joi.number().optional(), maxLatency: Joi.number().optional(), }), otherwise: Joi.alternatives().conditional("type", { is: "jrpc", then: Joi.object({ endpoint: Joi.string(), }), otherwise: Joi.object({ connectionFactory: Joi.object().custom((value, helpers) => { return value; // if (value instanceof ConnectionFactory) { // return value; // } // return helpers.message({"custom": "Invalid proxy connection"}); }), }), // otherwise: Joi.object().custom((value, helpers) => { // if (value instanceof ConnectionFactory) { // return value; // } // return helpers.message({"custom": "Invalid proxy connection"}); // }), }), }), }), ]), clientConfig: Joi.object({ message: Joi.object({}).optional().unknown(), initInput: Joi.any().optional(), }).optional(), tracing: Joi.object({ endpoint: Joi.string(), }).optional(), }).unknown(), ), mocha: Joi.object({ tsconfig: Joi.string().optional(), }).unknown(true), }).unknown(); export function loadConfig(configPath: string): LockliftConfig<ConfigState.INTERNAL> { const resolvedConfigPath = path.resolve(process.cwd(), configPath); if (!fs.existsSync(resolvedConfigPath)) { throw new commander.InvalidOptionArgumentError(`Config at ${configPath} not found!`); } const configFile = require(resolvedConfigPath); const validationResult = JoiConfig.validate(configFile.default); if (validationResult.error) { if (validationResult.error instanceof Joi.ValidationError) { throw new Error(validationResult.error.annotate()); } throw validationResult.error; } const config = { ...validationResult.value } as unknown as LockliftConfig<ConfigState.INTERNAL>; for (const [key, value] of Object.entries(config.networks)) { if (value.keys != null) { value.keys = { ...value.keys, phrase: value.keys?.phrase || "maze turn choose industry beauty sweet panther valve double report upset mother", path: value.keys.path || "m/44'/396'/0'/0/INDEX", }; } if (typeof value.connection === "string") { value.connection = getPresetParams(value.connection) || value.connection; } config.networks[key] = { ...value, giver: value.giver || { address: "0:ece57bcc6c530283becbbd8a3b24d3c5987cdddc3c8b7b33be6e4a6312490415", key: "172af540e43a524763dd53b26a066d472a97c4de37d5498170564510608250c3", }, }; } return config as unknown as LockliftConfig<ConfigState.INTERNAL>; } const getPresetParams = (preset: string): ConnectionData | undefined => { if (isKeyofNetworkPreset(preset)) { return NETWORK_PRESETS[preset]; } }; const isKeyofNetworkPreset = (preset: string): preset is keyof typeof NETWORK_PRESETS => { return Object.keys(NETWORK_PRESETS).find(el => el === preset) !== undefined; };