From 7fd0410c6e333ea6291bc34307ffbef54f4c7798 Mon Sep 17 00:00:00 2001 From: Hein Dauven Date: Sat, 14 Feb 2026 02:00:52 +0100 Subject: [PATCH 1/3] Add expand, clean, and completions commands Adds developer experience commands: macro expansion via cargo-expand, artifact cleanup, and shell completion generation. --- CHANGELOG.md | 1 + Cargo.toml | 1 + cli/Cargo.toml | 1 + cli/README.md | 4 +++ cli/src/cli.rs | 63 +++++++++++++++++++++++++++++++++ cli/src/commands/clean.rs | 29 +++++++++++++++ cli/src/commands/completions.rs | 16 +++++++++ cli/src/commands/expand.rs | 61 +++++++++++++++++++++++++++++++ cli/src/commands/mod.rs | 3 ++ cli/src/error.rs | 6 ++++ cli/src/main.rs | 3 ++ 11 files changed, 188 insertions(+) create mode 100644 cli/src/commands/clean.rs create mode 100644 cli/src/commands/completions.rs create mode 100644 cli/src/commands/expand.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e34726..5771ace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add the `dusk-forge` CLI with `new`, `build`, `test`, and `check` commands for contract project scaffolding and workflows. +- Add `expand`, `clean`, and `completions` commands to the `dusk-forge` CLI. ### Changed diff --git a/Cargo.toml b/Cargo.toml index 67df8b6..325ce19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ syn = { version = "2", features = ["full", "visit"] } assert_cmd = "2" cargo_metadata = "0.19" clap = { version = "4", features = ["derive"] } +clap_complete = "4" colored = "3" predicates = "3" tempfile = "=3.10.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 38416b1..3f71d00 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -13,6 +13,7 @@ path = "src/main.rs" [dependencies] cargo_metadata = { workspace = true } clap = { workspace = true, features = ["derive"] } +clap_complete = { workspace = true } colored = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } diff --git a/cli/README.md b/cli/README.md index 13d6e4c..f9bfbda 100644 --- a/cli/README.md +++ b/cli/README.md @@ -42,6 +42,9 @@ cargo run -p dusk-forge-cli -- --help - `dusk-forge build [target]`: build WASM artifacts. Targets: `all` (default), `contract`, `data-driver`. - `dusk-forge test [-- ]`: build contract WASM and run `cargo test --release`. - `dusk-forge check`: validate project structure and toolchain. +- `dusk-forge expand [--data-driver]`: show macro expansion with `cargo-expand`. +- `dusk-forge clean`: remove `target/contract` and `target/data-driver`. +- `dusk-forge completions `: generate shell completions. ## Common Options @@ -81,6 +84,7 @@ Data-driver builds require: Optional tools: - `wasm-opt` for smaller WASM artifacts +- `cargo-expand` for the `expand` command ## Template Notes diff --git a/cli/src/cli.rs b/cli/src/cli.rs index bff7668..36e2300 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use clap::{Args, Parser, Subcommand, ValueEnum}; +use clap_complete::Shell; use crate::build_runner::BuildTarget; @@ -49,6 +50,12 @@ pub enum Commands { Test(TestArgs), /// Validate project structure and toolchain. Check(ProjectOptions), + /// Show macro-expanded code using cargo-expand. + Expand(ExpandArgs), + /// Remove contract-specific build artifact directories. + Clean(ProjectOptions), + /// Generate shell completion scripts. + Completions(CompletionsArgs), } #[derive(Debug, Clone, Copy, ValueEnum)] @@ -109,3 +116,59 @@ pub struct TestArgs { /// Extra args passed through to `cargo test --release`. pub cargo_test_args: Vec, } + +#[derive(Debug, Args)] +pub struct ExpandArgs { + #[command(flatten)] + pub project: ProjectOptions, + + /// Expand with the data-driver feature. + #[arg(long)] + pub data_driver: bool, +} + +#[derive(Debug, Args)] +pub struct CompletionsArgs { + /// Shell to generate completions for. + #[arg(value_enum)] + pub shell: Shell, +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use clap::Parser; + + use super::{Cli, Commands}; + + #[test] + fn parses_expand_command() { + let cli = Cli::parse_from(["dusk-forge", "expand", "--data-driver"]); + + match cli.command { + Commands::Expand(args) => assert!(args.data_driver), + other => panic!("expected expand command, got {other:?}"), + } + } + + #[test] + fn parses_clean_command() { + let cli = Cli::parse_from(["dusk-forge", "clean", "--path", "demo"]); + + match cli.command { + Commands::Clean(args) => assert_eq!(args.path, PathBuf::from("demo")), + other => panic!("expected clean command, got {other:?}"), + } + } + + #[test] + fn parses_completions_command() { + let cli = Cli::parse_from(["dusk-forge", "completions", "bash"]); + + match cli.command { + Commands::Completions(_) => {} + other => panic!("expected completions command, got {other:?}"), + } + } +} diff --git a/cli/src/commands/clean.rs b/cli/src/commands/clean.rs new file mode 100644 index 0000000..c15f25e --- /dev/null +++ b/cli/src/commands/clean.rs @@ -0,0 +1,29 @@ +use std::fs; + +use crate::{ + cli::ProjectOptions, + error::Result, + project::{detect, metadata}, + ui, +}; + +pub fn run(args: ProjectOptions) -> Result<()> { + let project = metadata::load(&args.path)?; + detect::ensure_forge_project(&project.project_dir)?; + + remove_if_exists(&project.contract_target_dir)?; + remove_if_exists(&project.data_driver_target_dir)?; + + ui::success("Cleaned target/contract and target/data-driver"); + Ok(()) +} + +fn remove_if_exists(path: &std::path::Path) -> Result<()> { + if path.exists() { + fs::remove_dir_all(path)?; + ui::status(format!("Removed {}", path.display())); + } else { + ui::status(format!("Skipped {}, not present", path.display())); + } + Ok(()) +} diff --git a/cli/src/commands/completions.rs b/cli/src/commands/completions.rs new file mode 100644 index 0000000..ac79a55 --- /dev/null +++ b/cli/src/commands/completions.rs @@ -0,0 +1,16 @@ +use std::io; + +use clap::CommandFactory; +use clap_complete::generate; + +use crate::{ + cli::{Cli, CompletionsArgs}, + error::Result, +}; + +pub fn run(args: CompletionsArgs) -> Result<()> { + let mut cmd = Cli::command(); + let name = cmd.get_name().to_string(); + generate(args.shell, &mut cmd, name, &mut io::stdout()); + Ok(()) +} diff --git a/cli/src/commands/expand.rs b/cli/src/commands/expand.rs new file mode 100644 index 0000000..722b5c4 --- /dev/null +++ b/cli/src/commands/expand.rs @@ -0,0 +1,61 @@ +use std::process::{Command, Stdio}; + +use crate::{ + build_runner, + cli::ExpandArgs, + error::{CliError, Result}, + project::{detect, metadata}, + toolchain::{self, WASM_TARGET}, + tools, ui, +}; + +pub fn run(args: ExpandArgs) -> Result<()> { + let project = metadata::load(&args.project.path)?; + detect::ensure_forge_project(&project.project_dir)?; + + if tools::find_in_path("cargo-expand").is_none() { + return Err(CliError::MissingTool { + tool: "cargo-expand", + hint: "Install with: cargo install cargo-expand", + }); + } + + let feature = if args.data_driver { + "data-driver-js" + } else { + "contract" + }; + + ui::status(format!("Expanding macros with feature '{feature}'")); + + let mut cmd = Command::new("cargo"); + cmd.arg(toolchain::cargo_toolchain_arg(&project.project_dir)?) + .arg("expand") + .arg("--release") + .arg("--locked") + .arg("--features") + .arg(feature) + .arg("--target") + .arg(WASM_TARGET) + .arg("--manifest-path") + .arg(&project.manifest_path) + .current_dir(&project.project_dir) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .stdin(Stdio::inherit()); + build_runner::apply_local_forge_overrides(&mut cmd, args.project.verbose); + + if args.project.verbose { + eprintln!("Running: {}", ui::format_command(&cmd)); + } + + let status = cmd.status()?; + if !status.success() { + return Err(CliError::CommandFailed { + program: "cargo expand".to_string(), + code: status.code().unwrap_or(1), + }); + } + + Ok(()) +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 0c985c2..1817944 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -1,4 +1,7 @@ pub mod build; pub mod check; +pub mod clean; +pub mod completions; +pub mod expand; pub mod new; pub mod test; diff --git a/cli/src/error.rs b/cli/src/error.rs index 3485910..a8d75e4 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -18,6 +18,12 @@ pub enum CliError { #[error("expected a Dusk Forge contract project at {0}")] NotAForgeProject(PathBuf), + #[error("required tool not found: {tool}. {hint}")] + MissingTool { + tool: &'static str, + hint: &'static str, + }, + #[error("command failed: {program} (exit code {code})")] CommandFailed { program: String, code: i32 }, diff --git a/cli/src/main.rs b/cli/src/main.rs index 6b303e4..f7b4c7e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -27,5 +27,8 @@ fn run() -> Result<()> { Commands::Build(args) => commands::build::run(args), Commands::Test(args) => commands::test::run(args), Commands::Check(args) => commands::check::run(args), + Commands::Expand(args) => commands::expand::run(args), + Commands::Clean(args) => commands::clean::run(args), + Commands::Completions(args) => commands::completions::run(args), } } From cf87806ec8952c3b4a7f7494b14fd6748647e56a Mon Sep 17 00:00:00 2001 From: Hein Dauven Date: Sat, 14 Feb 2026 02:03:01 +0100 Subject: [PATCH 2/3] Add schema, call, and verify commands with wasmtime Adds data-driver WASM runtime (wasmtime) behind the 'schema' feature gate for extracting contract schemas, encoding call payloads, and verifying build artifacts with BLAKE3 hashes. --- CHANGELOG.md | 1 + Cargo.toml | 1 + cli/Cargo.toml | 7 ++ cli/README.md | 23 ++++- cli/src/cli.rs | 85 +++++++++++++++ cli/src/commands/call.rs | 64 ++++++++++++ cli/src/commands/mod.rs | 3 + cli/src/commands/schema.rs | 44 ++++++++ cli/src/commands/verify.rs | 119 +++++++++++++++++++++ cli/src/data_driver_wasm.rs | 201 ++++++++++++++++++++++++++++++++++++ cli/src/error.rs | 7 ++ cli/src/main.rs | 4 + 12 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 cli/src/commands/call.rs create mode 100644 cli/src/commands/schema.rs create mode 100644 cli/src/commands/verify.rs create mode 100644 cli/src/data_driver_wasm.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5771ace..6cc427e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add the `dusk-forge` CLI with `new`, `build`, `test`, and `check` commands for contract project scaffolding and workflows. - Add `expand`, `clean`, and `completions` commands to the `dusk-forge` CLI. +- Add `schema`, `call`, and `verify` commands to the `dusk-forge` CLI. ### Changed diff --git a/Cargo.toml b/Cargo.toml index 325ce19..8a9a894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ predicates = "3" tempfile = "=3.10.1" thiserror = "2" toml = "0.8" +wasmtime = "25" # Pin to match L1Contracts versions dusk-vm = { version = "=1.4.0", default-features = false } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3f71d00..54a6d98 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,9 +15,16 @@ cargo_metadata = { workspace = true } clap = { workspace = true, features = ["derive"] } clap_complete = { workspace = true } colored = { workspace = true } +blake3 = { workspace = true } serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } thiserror = { workspace = true } toml = { workspace = true } +wasmtime = { workspace = true, optional = true } + +[features] +default = ["schema"] +schema = ["dep:wasmtime"] [dev-dependencies] assert_cmd = { workspace = true } diff --git a/cli/README.md b/cli/README.md index f9bfbda..5a6fd6f 100644 --- a/cli/README.md +++ b/cli/README.md @@ -44,6 +44,9 @@ cargo run -p dusk-forge-cli -- --help - `dusk-forge check`: validate project structure and toolchain. - `dusk-forge expand [--data-driver]`: show macro expansion with `cargo-expand`. - `dusk-forge clean`: remove `target/contract` and `target/data-driver`. +- `dusk-forge schema [--pretty]`: build data-driver WASM and print `CONTRACT_SCHEMA` JSON. +- `dusk-forge call [--input ]`: encode call bytes using the data-driver export `encode_input_fn`. +- `dusk-forge verify [--expected-blake3 ] [--skip-build]`: validate artifacts, schema loading, and optional contract hash match. - `dusk-forge completions `: generate shell completions. ## Common Options @@ -67,6 +70,24 @@ dusk-forge build contract dusk-forge test ``` +Print schema JSON: + +```bash +dusk-forge schema --pretty +``` + +Encode input bytes for a function call: + +```bash +dusk-forge call set_count --input '42' +``` + +Verify artifacts and hash: + +```bash +dusk-forge verify --expected-blake3 +``` + ## Toolchain Requirements Contract builds require: @@ -100,4 +121,4 @@ Scaffolded projects include: - `rust-toolchain.toml` for deterministic toolchain selection - `Cargo.lock` generated at scaffold time -Build/test commands run Cargo with `--locked`. +Build/test/schema commands run Cargo with `--locked`. diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 36e2300..db86359 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -54,6 +54,12 @@ pub enum Commands { Expand(ExpandArgs), /// Remove contract-specific build artifact directories. Clean(ProjectOptions), + /// Build data-driver WASM and print CONTRACT_SCHEMA as JSON. + Schema(SchemaArgs), + /// Encode call input bytes through the data-driver. + Call(CallArgs), + /// Verify contract and data-driver artifacts. + Verify(VerifyArgs), /// Generate shell completion scripts. Completions(CompletionsArgs), } @@ -127,6 +133,43 @@ pub struct ExpandArgs { pub data_driver: bool, } +#[derive(Debug, Args)] +pub struct SchemaArgs { + #[command(flatten)] + pub project: ProjectOptions, + + /// Pretty-print JSON output. + #[arg(long)] + pub pretty: bool, +} + +#[derive(Debug, Args)] +pub struct CallArgs { + #[command(flatten)] + pub project: ProjectOptions, + + /// Contract function name to encode. + pub function: String, + + /// JSON input payload for the function (use `null` for no input). + #[arg(long, default_value = "null")] + pub input: String, +} + +#[derive(Debug, Args)] +pub struct VerifyArgs { + #[command(flatten)] + pub project: ProjectOptions, + + /// Optional expected BLAKE3 hash of the contract WASM. + #[arg(long)] + pub expected_blake3: Option, + + /// Skip rebuilding artifacts and verify existing files only. + #[arg(long)] + pub skip_build: bool, +} + #[derive(Debug, Args)] pub struct CompletionsArgs { /// Shell to generate completions for. @@ -171,4 +214,46 @@ mod tests { other => panic!("expected completions command, got {other:?}"), } } + + #[test] + fn parses_schema_command() { + let cli = Cli::parse_from(["dusk-forge", "schema", "--pretty"]); + + match cli.command { + Commands::Schema(args) => assert!(args.pretty), + other => panic!("expected schema command, got {other:?}"), + } + } + + #[test] + fn parses_call_command() { + let cli = Cli::parse_from(["dusk-forge", "call", "transfer", "--input", "{\"foo\":1}"]); + + match cli.command { + Commands::Call(args) => { + assert_eq!(args.function, "transfer"); + assert_eq!(args.input, "{\"foo\":1}"); + } + other => panic!("expected call command, got {other:?}"), + } + } + + #[test] + fn parses_verify_command() { + let cli = Cli::parse_from([ + "dusk-forge", + "verify", + "--expected-blake3", + "deadbeef", + "--skip-build", + ]); + + match cli.command { + Commands::Verify(args) => { + assert_eq!(args.expected_blake3.as_deref(), Some("deadbeef")); + assert!(args.skip_build); + } + other => panic!("expected verify command, got {other:?}"), + } + } } diff --git a/cli/src/commands/call.rs b/cli/src/commands/call.rs new file mode 100644 index 0000000..a596cd4 --- /dev/null +++ b/cli/src/commands/call.rs @@ -0,0 +1,64 @@ +use crate::{cli::CallArgs, error::Result}; + +#[cfg(feature = "schema")] +use crate::{ + build_runner::{self, BuildTarget}, + data_driver_wasm::DataDriverWasm, + project::{detect, metadata}, + toolchain, ui, +}; + +#[cfg(feature = "schema")] +pub fn run(args: CallArgs) -> Result<()> { + let project = metadata::load(&args.project.path)?; + detect::ensure_forge_project(&project.project_dir)?; + + toolchain::ensure_build(&project.project_dir, false)?; + + ui::status(format!( + "Building data-driver WASM for function '{}'", + args.function + )); + + let wasm_path = build_runner::build(&project, BuildTarget::DataDriver, args.project.verbose)?; + let optimized = + build_runner::wasm_opt::optimize_if_available(&wasm_path, args.project.verbose)?; + if !optimized { + ui::warn("wasm-opt not found, skipping optimization"); + } + + let mut driver = DataDriverWasm::load(&wasm_path)?; + let encoded = driver.encode_input(&args.function, &args.input)?; + + if args.project.verbose { + ui::status(format!( + "Encoded {} bytes for '{}'", + encoded.len(), + args.function + )); + } + + println!("{}", to_hex_prefixed(&encoded)); + ui::success("Call payload encoded"); + Ok(()) +} + +#[cfg(not(feature = "schema"))] +pub fn run(_args: CallArgs) -> Result<()> { + Err(crate::error::CliError::Message( + "call command is disabled (build with --features schema)".to_string(), + )) +} + +#[cfg(feature = "schema")] +fn to_hex_prefixed(bytes: &[u8]) -> String { + let mut out = String::with_capacity(bytes.len() * 2 + 2); + out.push_str("0x"); + + for byte in bytes { + use std::fmt::Write; + let _ = write!(&mut out, "{byte:02x}"); + } + + out +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 1817944..dc9135d 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -1,7 +1,10 @@ pub mod build; +pub mod call; pub mod check; pub mod clean; pub mod completions; pub mod expand; pub mod new; +pub mod schema; pub mod test; +pub mod verify; diff --git a/cli/src/commands/schema.rs b/cli/src/commands/schema.rs new file mode 100644 index 0000000..fa50c84 --- /dev/null +++ b/cli/src/commands/schema.rs @@ -0,0 +1,44 @@ +use crate::{cli::SchemaArgs, error::Result}; + +#[cfg(feature = "schema")] +use crate::{ + build_runner::{self, BuildTarget}, + data_driver_wasm::DataDriverWasm, + project::{detect, metadata}, + toolchain, ui, +}; + +#[cfg(feature = "schema")] +pub fn run(args: SchemaArgs) -> Result<()> { + let project = metadata::load(&args.project.path)?; + detect::ensure_forge_project(&project.project_dir)?; + + toolchain::ensure_build(&project.project_dir, false)?; + + ui::status("Building data-driver WASM"); + let wasm_path = build_runner::build(&project, BuildTarget::DataDriver, args.project.verbose)?; + let optimized = + build_runner::wasm_opt::optimize_if_available(&wasm_path, args.project.verbose)?; + if !optimized { + ui::warn("wasm-opt not found, skipping optimization"); + } + + let mut driver = DataDriverWasm::load(&wasm_path)?; + let schema_json = driver.get_schema_json()?; + let parsed: serde_json::Value = serde_json::from_str(&schema_json)?; + + if args.pretty { + println!("{}", serde_json::to_string_pretty(&parsed)?); + } else { + println!("{}", serde_json::to_string(&parsed)?); + } + + Ok(()) +} + +#[cfg(not(feature = "schema"))] +pub fn run(_args: SchemaArgs) -> Result<()> { + Err(crate::error::CliError::Message( + "schema command is disabled (build with --features schema)".to_string(), + )) +} diff --git a/cli/src/commands/verify.rs b/cli/src/commands/verify.rs new file mode 100644 index 0000000..dca0868 --- /dev/null +++ b/cli/src/commands/verify.rs @@ -0,0 +1,119 @@ +#[cfg(feature = "schema")] +use std::fs; + +use crate::{cli::VerifyArgs, error::Result}; + +#[cfg(feature = "schema")] +use crate::{ + build_runner::{self, BuildTarget}, + data_driver_wasm::DataDriverWasm, + error::CliError, + project::{detect, metadata}, + toolchain, ui, +}; + +#[cfg(feature = "schema")] +pub fn run(args: VerifyArgs) -> Result<()> { + let project = metadata::load(&args.project.path)?; + detect::ensure_forge_project(&project.project_dir)?; + + let contract_wasm = if args.skip_build { + project.contract_wasm_path.clone() + } else { + toolchain::ensure_build(&project.project_dir, true)?; + ui::status("Building contract WASM for verification"); + let wasm = build_runner::build(&project, BuildTarget::Contract, args.project.verbose)?; + let optimized = build_runner::wasm_opt::optimize_if_available(&wasm, args.project.verbose)?; + if !optimized { + ui::warn("wasm-opt not found, skipping optimization"); + } + wasm + }; + + let data_driver_wasm = if args.skip_build { + project.data_driver_wasm_path.clone() + } else { + toolchain::ensure_build(&project.project_dir, false)?; + ui::status("Building data-driver WASM for verification"); + let wasm = build_runner::build(&project, BuildTarget::DataDriver, args.project.verbose)?; + let optimized = build_runner::wasm_opt::optimize_if_available(&wasm, args.project.verbose)?; + if !optimized { + ui::warn("wasm-opt not found, skipping optimization"); + } + wasm + }; + + if !contract_wasm.exists() { + return Err(CliError::Message(format!( + "contract WASM not found: {}", + contract_wasm.display() + ))); + } + + if !data_driver_wasm.exists() { + return Err(CliError::Message(format!( + "data-driver WASM not found: {}", + data_driver_wasm.display() + ))); + } + + DataDriverWasm::validate_module(&contract_wasm)?; + ui::success(format!("Valid WASM module: {}", contract_wasm.display())); + + DataDriverWasm::validate_module(&data_driver_wasm)?; + ui::success(format!("Valid WASM module: {}", data_driver_wasm.display())); + + let contract_bytes = fs::read(&contract_wasm)?; + let actual_hash = blake3::hash(&contract_bytes).to_hex().to_string(); + + if let Some(expected) = args.expected_blake3 { + let expected_normalized = expected.trim_start_matches("0x").to_ascii_lowercase(); + if actual_hash != expected_normalized { + return Err(CliError::Message(format!( + "BLAKE3 mismatch: expected {expected_normalized}, got {actual_hash}" + ))); + } + ui::success("Contract BLAKE3 hash matches expected value"); + } + + let mut driver = DataDriverWasm::load(&data_driver_wasm)?; + let schema_json = driver.get_schema_json()?; + let schema: serde_json::Value = serde_json::from_str(&schema_json)?; + + let contract_name = schema + .get("name") + .and_then(serde_json::Value::as_str) + .ok_or_else(|| CliError::Message("schema is missing 'name'".to_string()))?; + + let function_count = schema + .get("functions") + .and_then(serde_json::Value::as_array) + .map(std::vec::Vec::len) + .ok_or_else(|| CliError::Message("schema is missing 'functions' array".to_string()))?; + + ui::success(format!( + "Schema loaded for {contract_name} with {function_count} function(s)" + )); + + if function_count == 0 { + return Err(CliError::Message( + "schema contains zero functions".to_string(), + )); + } + + println!("contract_wasm: {}", contract_wasm.display()); + println!("data_driver_wasm: {}", data_driver_wasm.display()); + println!("contract_blake3: {actual_hash}"); + println!("schema_contract: {contract_name}"); + println!("schema_functions: {function_count}"); + + ui::success("Verification passed"); + Ok(()) +} + +#[cfg(not(feature = "schema"))] +pub fn run(_args: VerifyArgs) -> Result<()> { + Err(crate::error::CliError::Message( + "verify command is disabled (build with --features schema)".to_string(), + )) +} diff --git a/cli/src/data_driver_wasm.rs b/cli/src/data_driver_wasm.rs new file mode 100644 index 0000000..ad960f5 --- /dev/null +++ b/cli/src/data_driver_wasm.rs @@ -0,0 +1,201 @@ +#[cfg(feature = "schema")] +use std::path::Path; + +#[cfg(feature = "schema")] +use wasmtime::{Engine, Instance, Memory, Module, Store, TypedFunc}; + +#[cfg(feature = "schema")] +use crate::error::{CliError, Result}; + +#[cfg(feature = "schema")] +pub struct DataDriverWasm { + store: Store<()>, + instance: Instance, + memory: Memory, +} + +#[cfg(feature = "schema")] +impl DataDriverWasm { + pub fn load(wasm_path: &Path) -> Result { + let engine = Engine::default(); + let module = Module::from_file(&engine, wasm_path)?; + + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &module, &[])?; + let memory = instance + .get_memory(&mut store, "memory") + .ok_or_else(|| CliError::Message("WASM export 'memory' not found".to_string()))?; + + let init = instance + .get_typed_func::<(), ()>(&mut store, "init") + .map_err(|_| CliError::Message("WASM export 'init' not found".to_string()))?; + init.call(&mut store, ())?; + + Ok(Self { + store, + instance, + memory, + }) + } + + pub fn get_schema_json(&mut self) -> Result { + let out_offset: usize = 64 * 1024; + let out_size: usize = 256 * 1024; + + self.ensure_memory_capacity((out_offset + out_size) as u64)?; + + let get_schema = self + .instance + .get_typed_func::<(i32, i32), i32>(&mut self.store, "get_schema") + .map_err(|_| CliError::Message("WASM export 'get_schema' not found".to_string()))?; + + let code = get_schema.call(&mut self.store, (out_offset as i32, out_size as i32))?; + if code != 0 { + let detail = self + .read_last_error() + .unwrap_or_else(|| "unknown error".to_string()); + return Err(CliError::Message(format!( + "get_schema failed with code {code}: {detail}" + ))); + } + + let bytes = self.read_prefixed_bytes(out_offset)?; + String::from_utf8(bytes) + .map_err(|err| CliError::Message(format!("schema output is not valid UTF-8: {err}"))) + } + + pub fn encode_input(&mut self, function: &str, json: &str) -> Result> { + let fn_name = function.as_bytes(); + let json = json.as_bytes(); + + let fn_offset = 1024usize; + let json_offset = align_up(fn_offset + fn_name.len() + 16, 8); + let out_offset = align_up(json_offset + json.len() + 16, 8); + let out_size = (json.len() * 2).max(4096); + + self.ensure_memory_capacity((out_offset + out_size) as u64)?; + + self.write_bytes(fn_offset, fn_name)?; + self.write_bytes(json_offset, json)?; + + let encode_input_fn = self + .instance + .get_typed_func::<(i32, i32, i32, i32, i32, i32), i32>( + &mut self.store, + "encode_input_fn", + ) + .map_err(|_| { + CliError::Message("WASM export 'encode_input_fn' not found".to_string()) + })?; + + let code = encode_input_fn.call( + &mut self.store, + ( + fn_offset as i32, + fn_name.len() as i32, + json_offset as i32, + json.len() as i32, + out_offset as i32, + out_size as i32, + ), + )?; + + if code != 0 { + let detail = self + .read_last_error() + .unwrap_or_else(|| "unknown error".to_string()); + return Err(CliError::Message(format!( + "encode_input_fn failed with code {code}: {detail}" + ))); + } + + self.read_prefixed_bytes(out_offset) + } + + pub fn validate_module(wasm_path: &Path) -> Result<()> { + let engine = Engine::default(); + let _ = Module::from_file(&engine, wasm_path)?; + Ok(()) + } + + fn read_last_error(&mut self) -> Option { + let get_last_error: TypedFunc<(i32, i32), i32> = self + .instance + .get_typed_func::<(i32, i32), i32>(&mut self.store, "get_last_error") + .ok()?; + + let out_offset: usize = 16 * 1024; + let out_size: usize = 8 * 1024; + + self.ensure_memory_capacity((out_offset + out_size) as u64) + .ok()?; + + let code = get_last_error + .call(&mut self.store, (out_offset as i32, out_size as i32)) + .ok()?; + if code != 0 { + return None; + } + + let bytes = self.read_prefixed_bytes(out_offset).ok()?; + String::from_utf8(bytes).ok() + } + + fn ensure_memory_capacity(&mut self, required_bytes: u64) -> Result<()> { + let current_pages = self.memory.size(&mut self.store); + let required_pages = required_bytes.div_ceil(65_536); + + if current_pages < required_pages { + self.memory + .grow(&mut self.store, required_pages - current_pages)?; + } + + Ok(()) + } + + fn write_bytes(&mut self, offset: usize, data: &[u8]) -> Result<()> { + let mem = self.memory.data_mut(&mut self.store); + let end = offset + data.len(); + if end > mem.len() { + return Err(CliError::Message(format!( + "WASM write out of bounds (offset={offset}, len={})", + data.len() + ))); + } + + mem[offset..end].copy_from_slice(data); + Ok(()) + } + + fn read_prefixed_bytes(&self, offset: usize) -> Result> { + let data = self.memory.data(&self.store); + + if offset + 4 > data.len() { + return Err(CliError::Message( + "WASM output buffer out of bounds".to_string(), + )); + } + + let len = u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]) as usize; + + let start = offset + 4; + let end = start + len; + if end > data.len() { + return Err(CliError::Message( + "WASM output exceeds memory bounds".to_string(), + )); + } + + Ok(data[start..end].to_vec()) + } +} + +#[cfg(feature = "schema")] +fn align_up(value: usize, align: usize) -> usize { + value.div_ceil(align) * align +} diff --git a/cli/src/error.rs b/cli/src/error.rs index a8d75e4..d0c3dc1 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -35,4 +35,11 @@ pub enum CliError { #[error("TOML parse error: {0}")] Toml(#[from] toml::de::Error), + + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + + #[cfg(feature = "schema")] + #[error("wasm runtime error: {0}")] + Wasm(#[from] wasmtime::Error), } diff --git a/cli/src/main.rs b/cli/src/main.rs index f7b4c7e..f827d8c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,7 @@ mod build_runner; mod cli; mod commands; +mod data_driver_wasm; mod error; mod project; mod template; @@ -29,6 +30,9 @@ fn run() -> Result<()> { Commands::Check(args) => commands::check::run(args), Commands::Expand(args) => commands::expand::run(args), Commands::Clean(args) => commands::clean::run(args), + Commands::Schema(args) => commands::schema::run(args), + Commands::Call(args) => commands::call::run(args), + Commands::Verify(args) => commands::verify::run(args), Commands::Completions(args) => commands::completions::run(args), } } From 383c36e45309c5b2a7bd4f41bd5be25b0a7c4157 Mon Sep 17 00:00:00 2001 From: Hein Dauven Date: Wed, 11 Mar 2026 20:14:31 +0100 Subject: [PATCH 3/3] workspace: move forge to edition 2024 and bump published Dusk deps --- CHANGELOG.md | 1 + Cargo.toml | 17 ++---- README.md | 10 ++-- cli/Cargo.toml | 2 +- cli/src/template/engine.rs | 2 +- cli/tests/new_command.rs | 2 +- contract-macro/src/data_driver.rs | 2 +- contract-macro/src/extract.rs | 38 ++++++------- contract-macro/src/generate.rs | 2 +- contract-macro/src/lib.rs | 6 +- contract-macro/src/parse.rs | 2 +- contract-macro/src/validate.rs | 55 ++++++++++--------- .../compile-fail-both-features/Cargo.toml | 2 +- contract-template/Cargo.toml | 20 ++----- rust-toolchain.toml | 2 +- tests/test-bridge/Cargo.toml | 8 +-- tests/test-bridge/tests/contract.rs | 4 +- tests/test-bridge/tests/schema.rs | 6 +- tests/test-bridge/tests/test_session.rs | 12 ++-- 19 files changed, 89 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cc427e..d1f1a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Make local forge path overrides opt-in for release builds and harden CLI template/path handling across platforms. +- Move the forge workspace and template project to Rust edition 2024 and bump published Dusk dependencies to their latest released versions. ## [0.2.2] - 2026-02-02 diff --git a/Cargo.toml b/Cargo.toml index 8a9a894..f4f6e25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dusk-forge" version = "0.2.2" -edition = "2021" +edition = "2024" description = "A smart contract development framework for Dusk" license = "MPL-2.0" repository = "https://github.com/dusk-network/forge" @@ -24,13 +24,13 @@ members = ["contract-macro", "tests/types", "tests/test-bridge", "cli"] exclude = ["contract-template"] [workspace.package] -edition = "2021" +edition = "2024" [workspace.dependencies] # Workspace internal dependencies dusk-forge-contract = { version = "0.1.1", path = "./contract-macro/" } -dusk-core = "1.4" +dusk-core = "1.4.1" bytecheck = { version = "0.6.12", default-features = false } dusk-bytes = "0.1.7" rkyv = { version = "0.7", default-features = false, features = [ @@ -50,20 +50,15 @@ cargo_metadata = "0.19" clap = { version = "4", features = ["derive"] } clap_complete = "4" colored = "3" +blake3 = "=1.5.4" predicates = "3" tempfile = "=3.10.1" thiserror = "2" toml = "0.8" wasmtime = "25" -# Pin to match L1Contracts versions -dusk-vm = { version = "=1.4.0", default-features = false } -# Pin to avoid edition2024 issues -blake2b_simd = "=1.0.2" -blake3 = "=1.5.4" -constant_time_eq = "=0.3.1" -time-core = "=0.1.6" -uuid = "=1.20.0" +# Pin to match the latest published Dusk runtime crates used by generated contracts. +dusk-vm = { version = "=1.4.3", default-features = false } # Enable overflow checks in release builds for contract safety [profile.release] diff --git a/README.md b/README.md index 6f1eac4..4e08986 100644 --- a/README.md +++ b/README.md @@ -258,13 +258,13 @@ All runtime dependencies go in the WASM-only section because contracts are gated ```toml [target.'cfg(target_family = "wasm")'.dependencies] -dusk-core = "1.4" -dusk-data-driver = { version = "0.3", optional = true } # Only for data-driver -dusk-forge = "0.1" +dusk-core = "1.4.1" +dusk-data-driver = { version = "0.3.1", optional = true } # Only for data-driver +dusk-forge = "0.2.2" [dev-dependencies] -dusk-core = "1.4" # Same types, but for host-side tests -dusk-vm = "0.1" # To run contract in tests +dusk-core = "1.4.1" # Same types, but for host-side tests +dusk-vm = "1.4.3" # To run contract in tests ``` ### Features diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 54a6d98..7f9c19a 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dusk-forge-cli" version = "0.1.0" -edition = "2021" +edition = "2024" description = "CLI for scaffolding and building Dusk Forge contracts" license = "MPL-2.0" repository = "https://github.com/dusk-network/forge" diff --git a/cli/src/template/engine.rs b/cli/src/template/engine.rs index 4a415b3..cb135c5 100644 --- a/cli/src/template/engine.rs +++ b/cli/src/template/engine.rs @@ -1,6 +1,6 @@ use crate::error::{CliError, Result}; -use super::embedded::{files, TemplateKind}; +use super::embedded::{TemplateKind, files}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ContractName { diff --git a/cli/tests/new_command.rs b/cli/tests/new_command.rs index 9f456fc..64e6911 100644 --- a/cli/tests/new_command.rs +++ b/cli/tests/new_command.rs @@ -38,7 +38,7 @@ fn new_scaffolds_counter_template() { assert!(test.contains("release/my_test.wasm")); assert!(!test.contains("YOUR_CONTRACT_NAME")); assert!(!test.contains("TODO")); - assert!(rust_toolchain.contains("channel = \"nightly-2024-07-30\"")); + assert!(rust_toolchain.contains("channel = \"nightly-2026-02-27\"")); } #[test] diff --git a/contract-macro/src/data_driver.rs b/contract-macro/src/data_driver.rs index 607bbe4..6765f2f 100644 --- a/contract-macro/src/data_driver.rs +++ b/contract-macro/src/data_driver.rs @@ -293,7 +293,7 @@ fn generate_decode_event_arms(events: &[EventInfo], type_map: &TypeMap) -> Vec, + feed_type: Option<&TokenStream2>, ) -> Result<(), syn::Error> { let feed_exprs = get_feed_exprs(method); @@ -52,7 +52,7 @@ fn validate_feeds( )); } - if let Some(ref ft) = feed_type { + if let Some(ft) = feed_type { // Has feeds attribute - validate it matches the expressions if let Some(mismatch_msg) = validate_feed_type_match(&ft.to_string(), &feed_exprs) { return Err(syn::Error::new_spanned(&method.sig, mismatch_msg)); @@ -156,7 +156,7 @@ pub(crate) fn trait_methods(trait_impl: &TraitImplInfo) -> Result Result, let receiver = extract_receiver(method); // Validate feed-related attributes - validate_feeds(method, &name, &feed_type)?; + validate_feeds(method, &name, feed_type.as_ref())?; // Extract parameters (name and type) let params = parameters(method); @@ -884,7 +884,7 @@ mod tests { } }; let name = format_ident!("stream_data"); - let result = validate_feeds(&method, &name, &None); + let result = validate_feeds(&method, &name, None); let Err(err) = result else { panic!("expected error for missing feeds attribute"); @@ -911,7 +911,7 @@ mod tests { }; let name = format_ident!("stream_multiple"); let feed_type: TokenStream2 = quote! { u64 }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); let Err(err) = result else { panic!("expected error for multiple feed calls"); @@ -936,7 +936,7 @@ mod tests { }; let name = format_ident!("stream_mismatch"); let feed_type: TokenStream2 = quote! { (u64, u64) }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); let Err(err) = result else { panic!("expected error for tuple mismatch"); @@ -958,7 +958,7 @@ mod tests { }; let name = format_ident!("stream_valid"); let feed_type: TokenStream2 = quote! { u64 }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); assert!(result.is_ok(), "valid feeds usage should not error"); } @@ -971,7 +971,7 @@ mod tests { } }; let name = format_ident!("regular_method"); - let result = validate_feeds(&method, &name, &None); + let result = validate_feeds(&method, &name, None); assert!( result.is_ok(), @@ -991,7 +991,7 @@ mod tests { }; let name = format_ident!("stream_in_loop"); let feed_type: TokenStream2 = quote! { u64 }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); // A single feed call inside a loop is valid assert!(result.is_ok(), "single feed call in loop should be valid"); @@ -1010,7 +1010,7 @@ mod tests { }; let name = format_ident!("stream_multiple_in_loop"); let feed_type: TokenStream2 = quote! { u64 }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); let Err(err) = result else { panic!("expected error for multiple feed calls in loop"); @@ -1034,7 +1034,7 @@ mod tests { }; let name = format_ident!("stream_conditional"); let feed_type: TokenStream2 = quote! { u64 }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); assert!( result.is_ok(), @@ -1056,7 +1056,7 @@ mod tests { }; let name = format_ident!("stream_branches"); let feed_type: TokenStream2 = quote! { u64 }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); let Err(err) = result else { panic!("expected error for feed calls in multiple branches"); @@ -1078,7 +1078,7 @@ mod tests { }; let name = format_ident!("stream_wants_tuple"); let feed_type: TokenStream2 = quote! { (u64, String) }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); let Err(err) = result else { panic!("expected error for tuple mismatch"); @@ -1097,7 +1097,7 @@ mod tests { }; let name = format_ident!("stream_sends_tuple"); let feed_type: TokenStream2 = quote! { u64 }; - let result = validate_feeds(&method, &name, &Some(feed_type)); + let result = validate_feeds(&method, &name, Some(&feed_type)); let Err(err) = result else { panic!("expected error for tuple mismatch"); diff --git a/contract-macro/src/generate.rs b/contract-macro/src/generate.rs index 91d4c67..7be7861 100644 --- a/contract-macro/src/generate.rs +++ b/contract-macro/src/generate.rs @@ -10,7 +10,7 @@ use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::{format_ident, quote}; use syn::{ImplItem, ItemImpl}; -use crate::{generate_arg_expr, EventInfo, FunctionInfo, ImportInfo, Receiver}; +use crate::{EventInfo, FunctionInfo, ImportInfo, Receiver, generate_arg_expr}; /// Generate the schema constant. pub(crate) fn schema( diff --git a/contract-macro/src/lib.rs b/contract-macro/src/lib.rs index 12d71a6..109a027 100644 --- a/contract-macro/src/lib.rs +++ b/contract-macro/src/lib.rs @@ -4,8 +4,6 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -#![feature(let_chains)] - //! Procedural macro for the `#[contract]` attribute. //! //! This macro is applied to a module containing a contract struct and its @@ -50,8 +48,8 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; use syn::{ - parse_macro_input, visit::Visit, Attribute, Expr, ExprCall, ExprLit, ExprPath, FnArg, - ImplItemFn, Item, ItemImpl, ItemMod, Lit, Type, + Attribute, Expr, ExprCall, ExprLit, ExprPath, FnArg, ImplItemFn, Item, ItemImpl, ItemMod, Lit, + Type, parse_macro_input, visit::Visit, }; // ============================================================================ diff --git a/contract-macro/src/parse.rs b/contract-macro/src/parse.rs index b046622..9f5173a 100644 --- a/contract-macro/src/parse.rs +++ b/contract-macro/src/parse.rs @@ -8,7 +8,7 @@ use syn::{ItemUse, UseTree}; -use crate::{is_relative_path_keyword, ImportExtraction, ImportInfo}; +use crate::{ImportExtraction, ImportInfo, is_relative_path_keyword}; /// Extract imports from a `use` statement. pub(crate) fn imports_from_use(item_use: &ItemUse) -> ImportExtraction { diff --git a/contract-macro/src/validate.rs b/contract-macro/src/validate.rs index 8f6ed6b..5852fdd 100644 --- a/contract-macro/src/validate.rs +++ b/contract-macro/src/validate.rs @@ -69,16 +69,16 @@ pub(crate) fn public_method(method: &ImplItemFn) -> Result<(), syn::Error> { } // Check for self receiver: if present, must be borrowed (not consumed) - if let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() { - if receiver.reference.is_none() { - return Err(syn::Error::new_spanned( - receiver, - format!( - "public method `{name}` cannot consume `self`; \ - use `&self` or `&mut self` instead" - ), - )); - } + if let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() + && receiver.reference.is_none() + { + return Err(syn::Error::new_spanned( + receiver, + format!( + "public method `{name}` cannot consume `self`; \ + use `&self` or `&mut self` instead" + ), + )); } Ok(()) @@ -403,9 +403,10 @@ mod tests { pub fn process(&self, value: T) -> T { value } }; let err = public_method(&method).unwrap_err(); - assert!(err - .to_string() - .contains("cannot have generic or const parameters")); + assert!( + err.to_string() + .contains("cannot have generic or const parameters") + ); } #[test] @@ -423,9 +424,10 @@ mod tests { pub fn process(&self) -> [u8; N] { [0; N] } }; let err = public_method(&method).unwrap_err(); - assert!(err - .to_string() - .contains("cannot have generic or const parameters")); + assert!( + err.to_string() + .contains("cannot have generic or const parameters") + ); } #[test] @@ -434,9 +436,10 @@ mod tests { pub fn process(&self, x: impl Display) {} }; let err = public_method(&method).unwrap_err(); - assert!(err - .to_string() - .contains("cannot use `impl Trait` in parameters")); + assert!( + err.to_string() + .contains("cannot use `impl Trait` in parameters") + ); } #[test] @@ -445,9 +448,10 @@ mod tests { pub fn iter(&self) -> impl Iterator { std::iter::empty() } }; let err = public_method(&method).unwrap_err(); - assert!(err - .to_string() - .contains("cannot use `impl Trait` as return type")); + assert!( + err.to_string() + .contains("cannot use `impl Trait` as return type") + ); } #[test] @@ -712,9 +716,10 @@ mod tests { fn process(&self, value: T) {} }; let err = trait_method(&method, "Processor", false).unwrap_err(); - assert!(err - .to_string() - .contains("cannot have generic or const parameters")); + assert!( + err.to_string() + .contains("cannot have generic or const parameters") + ); } #[test] diff --git a/contract-macro/tests/compile-fail-both-features/Cargo.toml b/contract-macro/tests/compile-fail-both-features/Cargo.toml index fb5e33b..4387fc7 100644 --- a/contract-macro/tests/compile-fail-both-features/Cargo.toml +++ b/contract-macro/tests/compile-fail-both-features/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "both-features-test" version = "0.0.0" -edition = "2021" +edition = "2024" publish = false [workspace] diff --git a/contract-template/Cargo.toml b/contract-template/Cargo.toml index 2f2d26c..4453165 100644 --- a/contract-template/Cargo.toml +++ b/contract-template/Cargo.toml @@ -38,34 +38,24 @@ [package] name = "YOUR_CONTRACT_NAME" version = "0.1.0" -edition = "2021" +edition = "2024" # ----------------------------------------------------------------------------- # WASM Dependencies (contract is gated by #![cfg(target_family = "wasm")]) # ----------------------------------------------------------------------------- [target.'cfg(target_family = "wasm")'.dependencies] -dusk-core = "1.4" -dusk-data-driver = { version = "0.3", optional = true } +dusk-core = "1.4.1" +dusk-data-driver = { version = "0.3.1", optional = true } dusk-forge = "0.2.2" # ----------------------------------------------------------------------------- # Dev Dependencies (for tests running on the host, not in WASM) # ----------------------------------------------------------------------------- [dev-dependencies] -dusk-core = "1.4" -dusk-vm = "1.4" +dusk-core = "1.4.1" +dusk-vm = "1.4.3" tempfile = "=3.10.1" -# Pin transitive crates to versions compatible with the current nightly toolchain. -# Keep these optional so they constrain resolution for generated projects -# without becoming direct runtime dependencies of the template. -[dependencies] -blake2b_simd = { version = "=1.0.2", default-features = false, optional = true } -blake3 = { version = "=1.5.4", default-features = false, optional = true } -constant_time_eq = { version = "=0.3.1", optional = true } -msgpacker = { version = "=0.4.3", default-features = false, optional = true } -time-core = { version = "=0.1.6", optional = true } - # ----------------------------------------------------------------------------- # Features # ----------------------------------------------------------------------------- diff --git a/rust-toolchain.toml b/rust-toolchain.toml index bc770a1..92da099 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2024-07-30" +channel = "nightly-2026-02-27" components = ["rust-src", "rustfmt", "cargo", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/tests/test-bridge/Cargo.toml b/tests/test-bridge/Cargo.toml index fd4f2ef..742a793 100644 --- a/tests/test-bridge/Cargo.toml +++ b/tests/test-bridge/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true [target.'cfg(target_family = "wasm")'.dependencies] dusk-core = { workspace = true } -dusk-data-driver = { version = "0.3", optional = true } +dusk-data-driver = { version = "0.3.1", optional = true } dusk-forge = { path = "../.." } types = { path = "../types" } serde_json = { workspace = true, default-features = false, features = [ @@ -22,12 +22,6 @@ rkyv = { workspace = true, features = ["validation"] } rusk-prover = "1.3" serde_json = { workspace = true, default-features = true } wasmtime = "25" -# Pin to avoid edition2024 issues (resolved via workspace) -blake2b_simd = { workspace = true } -blake3 = { workspace = true } -constant_time_eq = { workspace = true } -time-core = { workspace = true } -uuid = { workspace = true } # Transitive pin to keep generated test graphs on the same toolchain-compatible set. [features] # Contract WASM build diff --git a/tests/test-bridge/tests/contract.rs b/tests/test-bridge/tests/contract.rs index 8e57e16..19adc52 100644 --- a/tests/test-bridge/tests/contract.rs +++ b/tests/test-bridge/tests/contract.rs @@ -13,8 +13,8 @@ extern crate alloc; -use std::sync::mpsc; use std::sync::LazyLock; +use std::sync::mpsc; use dusk_core::abi::ContractId; use dusk_core::dusk; @@ -29,8 +29,8 @@ use types::{ EVMAddress, PendingWithdrawal, SetEVMAddressOrOffset, WithdrawalId, WithdrawalRequest, }; -use rand::rngs::StdRng; use rand::SeedableRng; +use rand::rngs::StdRng; const DEPLOYER: [u8; 64] = [0u8; 64]; diff --git a/tests/test-bridge/tests/schema.rs b/tests/test-bridge/tests/schema.rs index fec98a1..4396c7b 100644 --- a/tests/test-bridge/tests/schema.rs +++ b/tests/test-bridge/tests/schema.rs @@ -401,7 +401,9 @@ fn test_custom_data_driver_function_encode() { assert_eq!(encoded.len(), 20, "Expected 20 bytes for EVMAddress"); assert_eq!( encoded, - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + vec![ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 + ] ); } @@ -594,8 +596,8 @@ use std::sync::LazyLock; use dusk_core::abi::ContractId; use dusk_core::dusk; use dusk_core::signatures::bls::{PublicKey as AccountPublicKey, SecretKey as AccountSecretKey}; -use rand::rngs::StdRng; use rand::SeedableRng; +use rand::rngs::StdRng; use test_session::TestSession; use types::Address as DSAddress; use types::{Deposit, EVMAddress}; diff --git a/tests/test-bridge/tests/test_session.rs b/tests/test-bridge/tests/test_session.rs index 6721467..3c8ce53 100644 --- a/tests/test-bridge/tests/test_session.rs +++ b/tests/test-bridge/tests/test_session.rs @@ -12,7 +12,7 @@ use std::sync::mpsc; use dusk_core::abi::{ - ContractError, ContractId, Metadata, StandardBufSerializer, CONTRACT_ID_BYTES, + CONTRACT_ID_BYTES, ContractError, ContractId, Metadata, StandardBufSerializer, }; use dusk_core::signatures::bls::{PublicKey as AccountPublicKey, SecretKey as AccountSecretKey}; use dusk_core::stake::STAKE_CONTRACT; @@ -22,19 +22,19 @@ use dusk_core::transfer::phoenix::{ Note, NoteLeaf, NoteOpening, NoteTreeItem, PublicKey as ShieldedPublicKey, SecretKey as ShieldedSecretKey, }; -use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; +use dusk_core::transfer::{TRANSFER_CONTRACT, Transaction}; use dusk_core::{BlsScalar, JubJubScalar, LUX}; -use dusk_vm::{execute, CallReceipt, ContractData, Error as VMError, ExecutionConfig, Session, VM}; +use dusk_vm::{CallReceipt, ContractData, Error as VMError, ExecutionConfig, Session, VM, execute}; use ff::Field; use rkyv::bytecheck::CheckBytes; -use rkyv::ser::serializers::{BufferScratch, BufferSerializer, CompositeSerializer}; use rkyv::ser::Serializer; +use rkyv::ser::serializers::{BufferScratch, BufferSerializer, CompositeSerializer}; use rkyv::validation::validators::DefaultValidator; -use rkyv::{check_archived_root, Archive, Deserialize, Infallible, Serialize}; +use rkyv::{Archive, Deserialize, Infallible, Serialize, check_archived_root}; use rusk_prover::LocalProver; -use rand::rngs::StdRng; use rand::SeedableRng; +use rand::rngs::StdRng; const ZERO_ADDRESS: ContractId = ContractId::from_bytes([0; CONTRACT_ID_BYTES]); const GAS_LIMIT: u64 = 0x10_000_000;