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