Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add verify feature #194

Merged
merged 13 commits into from
Feb 12, 2024
8 changes: 6 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"name": "swanky-env",
"image": "ghcr.io/swankyhub/swanky-cli/swanky-base:swanky3.1.0-beta.0_v2.1.0",

"image": "ghcr.io/inkdevhub/swanky-cli/swanky-base:swanky3.1.0-beta.0_v2.1.1",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess your are using this feature to execute the official Parity image within the devcontainer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, is the verification working inside your devcontainer?

"version" : "latest"
pmikolajczyk41 marked this conversation as resolved.
Show resolved Hide resolved
}
},
// Mount the workspace volume
"mounts": ["source=${localWorkspaceFolder},target=/workspaces,type=bind,consistency=cached"],
"workspaceFolder": "/workspaces",
Expand Down
12 changes: 6 additions & 6 deletions base-image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ RUN curl -L https://github.com/swankyhub/swanky-cli/releases/download/v3.1.0-bet
# Install Rustup and Rust, additional components, packages, and verify the installations
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
/bin/bash -c "source $HOME/.cargo/env && \
rustup toolchain install nightly-2023-03-05 && \
rustup default nightly-2023-03-05 && \
rustup component add rust-src --toolchain nightly-2023-03-05 && \
rustup target add wasm32-unknown-unknown --toolchain nightly-2023-03-05 && \
cargo +stable install cargo-dylint dylint-link && \
cargo +stable install cargo-contract --force --version 4.0.0-alpha && \
rustup install 1.72 && \
rustup default 1.72 && \
rustup component add rust-src && \
rustup target add wasm32-unknown-unknown && \
cargo install cargo-dylint dylint-link && \
cargo install cargo-contract --version 4.0.0-rc.1 && \
rustc --version"

# Install Yarn 1.x
Expand Down
15 changes: 8 additions & 7 deletions src/commands/check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default class Check extends SwankyCommand<typeof Check> {
const cargoToml = TOML.parse(cargoTomlString);

const inkDependencies = Object.entries(cargoToml.dependencies)
.filter((dependency) => dependency[0].includes("ink_"))
.filter(([depName]) => /^ink($|_)/.test(depName))
.map(([depName, depInfo]) => {
const dependency = depInfo as Dependency;
return [depName, dependency.version ?? dependency.tag];
Expand All @@ -94,18 +94,19 @@ export default class Check extends SwankyCommand<typeof Check> {
const supportedInk = ctx.swankyConfig?.node.supportedInk;

const mismatched: Record<string, string> = {};
Object.entries(ctx.versions.contracts).forEach(([contract, inkPackages]) => {
Object.entries(inkPackages).forEach(([inkPackage, version]) => {
if (semver.gt(version, supportedInk!)) {
Object.entries(ctx.versions.contracts).forEach(([contract, contractData]) => {
if (Object.prototype.hasOwnProperty.call(contractData, "ink")) {
const version = contractData.ink;
if (version && semver.gt(version, supportedInk!)) {
mismatched[
`${contract}-${inkPackage}`
] = `Version of ${inkPackage} (${version}) in ${contract} is higher than supported ink version (${supportedInk})`;
`${contract}-ink`
] = `Version of ink (${version}) in ${contract} is higher than supported ink version (${supportedInk})`;
}

if (!(version.startsWith("=") || version.startsWith("v"))) {
ctx.looseDefinitionDetected = true;
}
});
}
});

ctx.mismatchedVersions = mismatched;
Expand Down
24 changes: 21 additions & 3 deletions src/commands/contract/compile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Args, Flags } from "@oclif/core";
import path from "node:path";
import { storeArtifacts, Spinner, generateTypes } from "../../lib/index.js";
import { storeArtifacts, Spinner, generateTypes, checkCargoVersion } from "../../lib/index.js";
import { spawn } from "node:child_process";
import { pathExists } from "fs-extra/esm";
import { SwankyCommand } from "../../lib/swankyCommand.js";
Expand All @@ -16,6 +16,12 @@ export class CompileContract extends SwankyCommand<typeof CompileContract> {
description:
"A production contract should always be build in `release` mode for building optimized wasm",
}),
verifiable: Flags.boolean({
default: false,
char: "v",
pmikolajczyk41 marked this conversation as resolved.
Show resolved Hide resolved
description:
"A production contract should be build in `verifiable` mode to deploy on a public network. Ensure Docker Engine is up and running.",
}),
all: Flags.boolean({
default: false,
char: "a",
Expand Down Expand Up @@ -65,11 +71,15 @@ export class CompileContract extends SwankyCommand<typeof CompileContract> {
"contract",
"build",
"--manifest-path",
`${contractPath}/Cargo.toml`,
`contracts/${contractName}/Cargo.toml`,
ipapandinas marked this conversation as resolved.
Show resolved Hide resolved
];
if (flags.release) {
if (flags.release && !flags.verifiable) {
compileArgs.push("--release");
}
if (flags.verifiable) {
checkCargoVersion("4.0.0", ["4.0.0-alpha"]);
pmikolajczyk41 marked this conversation as resolved.
Show resolved Hide resolved
compileArgs.push("--verifiable");
ipapandinas marked this conversation as resolved.
Show resolved Hide resolved
}
const compile = spawn("cargo", compileArgs);
this.logger.info(`Running compile command: [${JSON.stringify(compile.spawnargs)}]`);
let outputBuffer = "";
Expand Down Expand Up @@ -114,6 +124,14 @@ export class CompileContract extends SwankyCommand<typeof CompileContract> {
`Generating ${contractName} contract ts types`,
`${contractName} contract's TS types Generated successfully`
);

this.swankyConfig.contracts[contractName].build = {
timestamp: Date.now(),
artifactsPath,
isVerified: false,
};

await this.storeConfig();
}
}
}
110 changes: 110 additions & 0 deletions src/commands/contract/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Args, Flags } from "@oclif/core";
import path from "node:path";
import { checkCargoVersion, Spinner } from "../../lib/index.js";
import { pathExists } from "fs-extra/esm";
import { SwankyCommand } from "../../lib/swankyCommand.js";
import { ConfigError, InputError, ProcessError } from "../../lib/errors.js";
import { spawn } from "node:child_process";

export class VerifyContract extends SwankyCommand<typeof VerifyContract> {
static description = "Verify the smart contract(s) in your contracts directory";

static flags = {
all: Flags.boolean({
default: false,
char: "a",
description: "Set all to true to verify all contracts",
}),
};

static args = {
contractName: Args.string({
name: "contractName",
required: false,
default: "",
description: "Name of the contract to verify",
}),
};

ipapandinas marked this conversation as resolved.
Show resolved Hide resolved
async run(): Promise<void> {
const { args, flags } = await this.parse(VerifyContract);

ipapandinas marked this conversation as resolved.
Show resolved Hide resolved
checkCargoVersion("4.0.0", ["4.0.0-alpha"]);

if (args.contractName === undefined && !flags.all) {
ipapandinas marked this conversation as resolved.
Show resolved Hide resolved
throw new InputError("No contracts were selected to verify", { winston: { stack: true } });
}

const contractNames = flags.all
? Object.keys(this.swankyConfig.contracts)
: [args.contractName];

const spinner = new Spinner();

for (const contractName of contractNames) {
this.logger.info(`Started compiling contract [${contractName}]`);
const contractInfo = this.swankyConfig.contracts[contractName];
if (!contractInfo) {
throw new ConfigError(
`Cannot find contract info for ${contractName} contract in swanky.config.json`
);
}
const contractPath = path.resolve("contracts", contractInfo.name);
this.logger.info(`"Looking for contract ${contractInfo.name} in path: [${contractPath}]`);
if (!(await pathExists(contractPath))) {
throw new InputError(`Contract folder not found at expected path`);
}

if(!contractInfo.build) {
throw new InputError(`Contract ${contractName} is not compiled. Please compile it first`);
}

pmikolajczyk41 marked this conversation as resolved.
Show resolved Hide resolved
await spinner.runCommand(
async () => {
return new Promise<boolean>((resolve, reject) => {
const compileArgs = [
"contract",
"verify",
`artifacts/${contractName}/${contractName}.contract`,
"--manifest-path",
`contracts/${contractName}/Cargo.toml`,
];
const compile = spawn("cargo", compileArgs);
this.logger.info(`Running verify command: [${JSON.stringify(compile.spawnargs)}]`);
let outputBuffer = "";
let errorBuffer = "";

compile.stdout.on("data", (data) => {
outputBuffer += data.toString();
spinner.ora.clear();
});

compile.stderr.on("data", (data) => {
errorBuffer += data;
});

compile.on("exit", (code) => {
if (code === 0) {
const regex = /Successfully verified contract (.*) against reference contract (.*)/;
const match = outputBuffer.match(regex);
if (match) {
this.logger.info(`Contract ${contractName} verification done.`);
resolve(true);
}
} else {
reject(new ProcessError(errorBuffer));
}
});
});
},
`Verifying ${contractName} contract`,
`${contractName} Contract verified successfully`
);
contractInfo.build.isVerified = true;

this.swankyConfig.contracts[contractName] = contractInfo;

await this.storeConfig();
}
}
}
2 changes: 1 addition & 1 deletion src/lib/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class Contract {
`Cannot read .contract bundle, path not found: ${check.missingPaths.toString()}`
);
}
return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.contract`));
return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.contract`), 'utf-8');
}

async getWasm(): Promise<Buffer> {
Expand Down
29 changes: 27 additions & 2 deletions src/lib/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execaCommand } from "execa";
import { execaCommand, execaCommandSync } from "execa";
import { ensureDir, copy, remove } from "fs-extra/esm";
import { rename, readFile, rm, writeFile } from "fs/promises";
import path from "node:path";
Expand All @@ -10,7 +10,8 @@ import { nodeInfo } from "./nodeInfo.js";
import decompress from "decompress";
import { Spinner } from "./spinner.js";
import { SupportedPlatforms, SupportedArch } from "../types/index.js";
import { ConfigError, NetworkError } from "./errors.js";
import { ConfigError, InputError, NetworkError } from "./errors.js";
import semver from "semver";

export async function checkCliDependencies(spinner: Spinner) {
const dependencyList = [
Expand Down Expand Up @@ -136,3 +137,27 @@ export async function installDeps(projectPath: string) {
await execaCommand(installCommand, { cwd: projectPath });
}
}

export function checkCargoVersion(minimalVersion: string, invalidVersionsList: string[]) {
pmikolajczyk41 marked this conversation as resolved.
Show resolved Hide resolved
const regex = /cargo-contract-contract (.*)-unknown-(.*)/;
let cargoVersion;
try {
const result = execaCommandSync("cargo contract -V");
cargoVersion = result.stdout;
} catch {
cargoVersion = null;
}
if (cargoVersion) {
const match = cargoVersion.match(regex);
if (match) {
cargoVersion = match[1];
}
} else {
throw new InputError(`Verifiable mode requires cargo-contract version >= ${minimalVersion}`);
}
if (!cargoVersion || semver.lt(cargoVersion, "4.0.0-rc") || invalidVersionsList.includes(cargoVersion)) {
throw new InputError(
`Verifiable mode requires cargo-contract version >= ${minimalVersion}`
);
}
}
3 changes: 2 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface ChainProperty {

export type ExtrinsicPayload = SubmittableExtrinsic<"promise">;

export interface Encrypted { iv: string; data: string };
export interface Encrypted { iv: string; data: string }

export interface AccountData {
isDev: boolean;
Expand All @@ -30,6 +30,7 @@ export interface ContractData {
export interface BuildData {
timestamp: number;
artifactsPath: string;
isVerified: boolean;
}

export interface DeploymentData {
Expand Down
Loading