diff --git a/src/source-verifier/tact-source-verifier.ts b/src/source-verifier/tact-source-verifier.ts index 1b29164..1dea85a 100644 --- a/src/source-verifier/tact-source-verifier.ts +++ b/src/source-verifier/tact-source-verifier.ts @@ -1,6 +1,6 @@ import path from "path"; import semver from "semver"; -import type { verify as VerifyFunctionLegacy } from "tact-1.4.0"; +import type { TactLogger, verify as VerifyFunctionLegacy } from "tact-1.4.0"; import { Logger, PackageFileFormat } from "tact-1.4.1"; import type { verify as VerifyFunction } from "tact-1.5.2"; import { Cell } from "ton"; @@ -8,6 +8,7 @@ import { getSupportedVersions } from "../fetch-compiler-versions"; import { CompileResult, SourceVerifier, SourceVerifyPayload } from "../types"; import { timeoutPromise } from "../utils"; import { getLogger } from "../logger"; +import { verifyTactNew } from "./verify"; const logger = getLogger("tact-source-verifier"); @@ -33,6 +34,24 @@ class OutputAppendingLogger extends Logger { } } +type VerifyFunctionType = + | typeof VerifyFunctionLegacy + | typeof VerifyFunction + | ReturnType; + +async function dispatchVerify(version: string, output: string[]): Promise { + try { + if (version < "1.6.0") { + return (await import(`tact-${version}`)).verify; + } else { + return await verifyTactNew(version); + } + } catch (e) { + output.push(`Failed to load tact v${version}. It probably doesn't exist on the server.`); + throw e; + } +} + export class TactSourceVerifier implements SourceVerifier { fileSystem: FileSystem; @@ -97,26 +116,18 @@ export class TactSourceVerifier implements SourceVerifier { throw new Error("Unsupported tact version: " + pkgParsed.compiler.version); } - const verify: typeof VerifyFunctionLegacy | typeof VerifyFunction = await import( - `tact-${pkgParsed.compiler.version}` - ) - .then((m) => m.verify) - .catch((e) => { - output.push( - `Failed to load tact v${pkgParsed.compiler.version}. It probably doesn't exist on the server.`, - ); - throw e; - }); + const verify = await dispatchVerify(pkgParsed.compiler.version, output); let vPromise; if (this.isLegacyLogger(verify, pkgParsed.compiler.version)) { + const logger: TactLogger = { + log: (message: string) => output.push(message), + error: (message: string) => output.push(message), + }; vPromise = verify({ pkg, - logger: { - log: (message: string) => output.push(message), - error: (message: string) => output.push(message), - }, + logger, }); } else { vPromise = verify({ diff --git a/src/source-verifier/verify.ts b/src/source-verifier/verify.ts new file mode 100644 index 0000000..25beab6 --- /dev/null +++ b/src/source-verifier/verify.ts @@ -0,0 +1,107 @@ +import normalize from "path-normalize"; +import { Cell } from "@ton/core"; +import type { Config, Options, ILogger, PackageFileFormat } from "tact-1.6.0"; + +export type VerifyResult = + | { + ok: true; + package: PackageFileFormat; + files: Record; + } + | { + ok: false; + error: + | "invalid-package-format" + | "invalid-compiler" + | "invalid-compiler-version" + | "compilation-failed" + | "verification-failed"; + }; + +export async function verifyTactNew(version: string) { + const { Logger, run } = (await import(`tact-${version}`)) as typeof import("tact-1.6.0"); + const { fileFormat } = (await import( + `tact-${version}/dist/packaging/fileFormat` + )) as typeof import("tact-1.6.0/dist/packaging/fileFormat"); + const { getCompilerVersion } = (await import( + `tact-${version}/dist/pipeline/version` + )) as typeof import("tact-1.6.0/dist/pipeline/version"); + + return async function verify(args: { + pkg: string; + logger?: ILogger | null | undefined; + }): Promise { + const logger: ILogger = args.logger ?? new Logger(); + + // Loading package + let unpacked: PackageFileFormat; + try { + const data = JSON.parse(args.pkg); + unpacked = fileFormat.parse(data); + } catch (_) { + return { ok: false, error: "invalid-package-format" }; + } + + if (unpacked.sources === undefined) { + return { ok: false, error: "invalid-package-format" }; + } + + // Check compiler and version + if (unpacked.compiler.name !== "tact") { + return { ok: false, error: "invalid-compiler" }; + } + if (unpacked.compiler.version !== getCompilerVersion()) { + return { ok: false, error: "invalid-compiler-version" }; + } + + // Create a options + if (!unpacked.compiler.parameters) { + return { ok: false, error: "invalid-package-format" }; + } + const params = JSON.parse(unpacked.compiler.parameters); + if (typeof params.entrypoint !== "string") { + return { ok: false, error: "invalid-package-format" }; + } + const options: Options = params.options || {}; + const entrypoint: string = params.entrypoint; + + // Create config + const config: Config = { + projects: [ + { + name: "verifier", + path: normalize("./contract/" + entrypoint), + output: "./output", + options, + }, + ], + }; + + // Build + const files: Record = {}; + for (const [name, source] of Object.entries(unpacked.sources)) { + files["contract/" + name] = source; + } + + const result = await run({ config, files, logger }); + if (!result.ok) { + return { ok: false, error: "compilation-failed" }; + } + + // Read output + const compiledCell = files["output/verifier_" + unpacked.name + ".code.boc"]; + if (!compiledCell) { + return { ok: false, error: "verification-failed" }; + } + + // Check output + const a = Cell.fromBase64(compiledCell); + const b = Cell.fromBase64(unpacked.code); + if (!a.equals(b)) { + return { ok: false, error: "verification-failed" }; + } + + // Return + return { ok: true, package: unpacked, files }; + }; +}