Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ten-jobs-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/devnet": patch
---

remove cannon
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
},
"devDependencies": {
"@biomejs/biome": "^2.2.4",
"@cartesi/devnet": "2.0.0-alpha.8",
"@cartesi/devnet": "workspace:*",
"@cartesi/rollups": "2.1.1",
"@types/bun": "^1.3.5",
"@types/bytes": "^3.1.5",
Expand Down
15 changes: 0 additions & 15 deletions apps/cli/wagmi.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { type Plugin, defineConfig } from "@wagmi/cli";
import { readFileSync, readdirSync } from "node:fs";
import path from "node:path";
import type { Abi } from "viem";

const DataAvailability = JSON.parse(
readFileSync(
"node_modules/@cartesi/rollups/out/DataAvailability.sol/DataAvailability.json",
"utf8",
),
);

interface CannonOptions {
directory: string;
Expand Down Expand Up @@ -67,16 +59,9 @@ const cannonDeployments = (config: CannonOptions): Plugin => {

export default defineConfig({
out: "src/contracts.ts",
contracts: [
{
name: "DataAvailability",
abi: DataAvailability.abi as Abi,
},
],
plugins: [
cannonDeployments({
directory: "node_modules/@cartesi/devnet/deployments",
includes: [/^cartesi*/, /^Test*/],
}),
],
});
408 changes: 7 additions & 401 deletions bun.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/devnet/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
anvil_state.json
broadcast
cache
dependencies
deployments
Expand Down
88 changes: 88 additions & 0 deletions packages/devnet/anvil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import pRetry from "p-retry";

/**
* Get the installed anvil version
* @returns the installed anvil version
*/
export const version = async () => {
const proc = Bun.spawn(["anvil", "--version"]);
const output = await proc.stdout.text();

// anvil Version: 1.4.3-v1.4.3
// Commit SHA: fa9f934bdac4bcf57e694e852a61997dda90668a
// Build Timestamp: 2025-10-22T04:37:38.758664000Z (1761107858)
// Build Profile: maxperf

// parse the output to get the version
const versionMatch = output.match(
/Version: (\d+\.\d+\.\d+)-v(\d+\.\d+\.\d+)/,
);
if (!versionMatch) {
throw new Error("Failed to parse anvil version. Is anvil installed?");
}
return versionMatch[1];
};

type StartOptions = {
chainId?: number;
loadState: string;
dumpState: string;
};

/**
* Start an anvil instance
* @param options - The options for starting anvil
* @returns The child process of the anvil instance
*/
export const start = async (options: StartOptions) => {
const chainId = options.chainId ?? 31337;
// spawn anvil child process
const controller = new AbortController();
const proc = Bun.spawn(
[
"anvil",
"--load-state",
options.loadState,
"--preserve-historical-states",
"--quiet",
"--dump-state",
options.dumpState,
],
{ signal: controller.signal, stdout: "inherit", stderr: "inherit" },
);
process.on("SIGINT", () => controller.abort());
process.on("SIGTERM", () => controller.abort());

// wait for anvil to be responding
await pRetry(
async () => {
const cid = Bun.spawnSync(["cast", "chain-id"])
.stdout.toString()
.trim();
if (cid !== chainId.toString()) {
throw new Error("Anvil is not responding");
}
return cid;
},
{ retries: 10, minTimeout: 100 },
);

return proc;
};

/**
* Stop an anvil instance
* @param proc - The child process of the anvil instance
* @returns The exit code of the anvil instance
*/
export const stop = async (proc: Bun.Subprocess) => {
// send a graceful shutdown signal
proc.kill("SIGTERM");

// check exit code
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`Anvil exited with code ${exitCode}`);
}
return exitCode;
};
197 changes: 197 additions & 0 deletions packages/devnet/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { semver } from "bun";
import { existsSync, readdirSync } from "fs-extra";
import { Listr, type ListrTask } from "listr2";
import * as anvil from "./anvil";
import { downloadAndExtract } from "./download";
import path = require("node:path");

const ANVIL_VERSION = "1.4.3";
const ROLLUPS_VERSION = "2.1.1";
const PRT_VERSION = "2.0.1";

/**
* Tasks to download and extract dependencies
*/
const dependencies: ListrTask[] = [
{
url: `https://github.com/cartesi/dave/releases/download/v${PRT_VERSION}/cartesi-rollups-prt-anvil-v${ANVIL_VERSION}.tar.gz`,
destination: "build",
},
{
url: `https://github.com/cartesi/dave/releases/download/v${PRT_VERSION}/cartesi-rollups-prt-contract-artifacts.tar.gz`,
destination: "out",
stripComponents: 3,
},
{
url: `https://github.com/cartesi/rollups-contracts/releases/download/v${ROLLUPS_VERSION}/rollups-contracts-${ROLLUPS_VERSION}-artifacts.tar.gz`,
destination: "out",
},
].map((file) => ({
title: `${file.url} -> ${file.destination}`,
task: async () => await downloadAndExtract(file),
}));

/**
* Deploy contracts using forge script
* @param options - The options for deploying contracts
* @param options.privateKey - The private key to use for the deployment
* @param options.rpcUrl - The RPC URL to use for the deployment
* @returns
*/
const deploy = async (options: { privateKey: string; rpcUrl: string }) => {
// execute forge script
const proc = Bun.spawn(
[
"forge",
"script",
"Deploy",
"--broadcast",
"--non-interactive",
"--private-key",
options.privateKey,
"--rpc-url",
options.rpcUrl,
"--slow",
],
{ stdio: ["ignore", "pipe", "pipe"] },
);
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`Forge script exited with code ${exitCode}`, {
cause: await proc.stderr.text(),
});
}
return exitCode;
};

/**
* Generate artifacts from deployed contracts (with { abi, address, contractName })
* @returns
*/
const artifacts = (dir: string) => {
return readdirSync(dir).map((file) => ({
title: file,
task: async () => {
const deployment = await Bun.file(path.join(dir, file)).json();
const { address, contractName } = deployment;

const filename = path.join(
"out",
`${contractName}.sol`,
`${contractName}.json`,
);

// read abi from forge artifact (if it exists, error if it doesn't)
const { abi } = existsSync(filename)
? await Bun.file(filename).json()
: undefined;
if (!abi) {
throw new Error(`ABI file not found for ${contractName}`);
}

// write deployments (with { abi, address, contractName })
const artifact = path.join("deployments", `${contractName}.json`);
return Bun.write(
artifact,
JSON.stringify({ abi, address, contractName }, null, 2),
);
},
}));
};

const build = async () => {
type Ctx = {
anvilProc?: Bun.Subprocess;
dumpState: string;
privateKey: string;
rpcUrl: string;
};

const tasks = new Listr<Ctx>(
[
{
title: "Checking anvil version",
task: async (_, task) => {
// check is required anvil version is installed
const anvilVersion = await anvil.version();
if (!semver.satisfies(anvilVersion, ANVIL_VERSION)) {
throw new Error(
`Anvil version ${anvilVersion} is not the expected version ${ANVIL_VERSION}`,
);
}
task.title = `Anvil version ${anvilVersion} is installed`;
},
},
{
title: "Download dependencies",
task: async (_, task) =>
task.newListr(dependencies, { concurrent: true }),
},
{
title: "Starting anvil...",
task: async (ctx, task) => {
const { dumpState } = ctx;

// start anvil
ctx.anvilProc = await anvil.start({
loadState: `build/state.json`,
dumpState,
});

// setup graceful anvil shutdown, just in case process is terminated prematurely
const shutdown = async () => {
await anvil.stop(ctx.anvilProc);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);

task.title = `Anvil started (PID: ${ctx.anvilProc.pid})`;
},
},
{
title: "Deploying contracts...",
task: async (ctx, task) => {
const { privateKey, rpcUrl } = ctx;
await deploy({ privateKey, rpcUrl });
task.title = "Contracts deployed";
},
},
{
title: "Generating artifacts",
task: async (_, task) =>
task.newListr(
artifacts(path.join("build", "deployments", "31337")),
{ concurrent: true },
),
},
{
title: "Stopping anvil...",
task: async (ctx, task) => {
if (ctx.anvilProc) {
// kill anvil gracefully
await anvil.stop(ctx.anvilProc);
}
task.title = `Anvil stopped -> ${ctx.dumpState}`;
},
},
],
{ exitOnError: false },
);

await tasks.run({
privateKey:
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
rpcUrl: "http://127.0.0.1:8545",
dumpState: "anvil_state.json",
});

// exit with error code if any task failed
const failed = tasks.tasks.some((task) => task.hasFailed());
if (failed) {
process.exit(1);
}
};

build().catch(() => {
process.exit(1);
});
21 changes: 0 additions & 21 deletions packages/devnet/cannonfile.toml

This file was deleted.

Loading