Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add `#[contract(emits = [...])]` method-level attribute for manual event registration, covering both trait impls with default implementations and inherent methods that delegate to helpers in other crates.
- Add compile error when a public `&mut self` method emits no events. Suppress with `#[contract(no_event)]`.
- Add compile-time signature validation for custom data-driver handlers registered via `#[contract(encode_input = …)]`, `decode_input`, and `decode_output`. Mismatched argument or return types surface as a clear `compile_error!` at the handler definition, naming the handler, the role, and the expected signature. Idiomatic short paths (`Vec<u8>`, `Error`, `JsonValue` after a `use`) are canonicalised through the contract module's import map and accepted against the role's canonical form; `'static` lifetimes in handler references are rejected with a pointer to drop them or declare a handler-generic lifetime.
- Add detection of variable identifiers used as `abi::emit()` topics (warning pending `proc_macro_diagnostic` stabilisation).
- 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.
Expand All @@ -26,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Make `dusk-forge build data-driver` select the supported project feature (`data-driver-js` or `data-driver`) instead of hardcoding the JS variant.
- Replace the vague `"custom handler required: {fn}"` runtime error emitted at each of the three data-driver dispatch sites with a role-specific message that names the missing handler's role (`encode_input`, `decode_input`, `decode_output`) and the expected handler signature in concrete types.
- Re-emit the contract module's `use` items inside the generated `data_driver` submodule so custom data-driver handlers written with idiomatic short paths (`Vec<u8>`, `Error`, `JsonValue` after a `use`) compile end-to-end — the spliced handler body now resolves the same names it did at its original site. Only imports referenced by a handler are carried over, so contract-only imports don't leak into the submodule. The built-in submodule scaffolding also switches to fully-qualified `alloc::vec::Vec` / `alloc::string::String` so a user `use` of the short forms doesn't collide with a preluded import.

## [0.2.2] - 2026-02-02

Expand Down
472 changes: 446 additions & 26 deletions contract-macro/src/data_driver.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions contract-macro/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,9 @@ pub(crate) fn contract_data<'a>(

let trait_impls = trait_impls(items, &name);
let custom_handlers = custom_data_driver_handlers(items);
for handler in &custom_handlers {
validate::custom_handler(handler, &imports)?;
}

Ok(ContractData {
imports,
Expand Down
3 changes: 2 additions & 1 deletion contract-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,8 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
let type_map = resolve::build_type_map(&imports, &functions, &events);

// Generate data_driver module at crate root level (outside contract module)
let data_driver = data_driver::module(&type_map, &functions, &events, &custom_handlers);
let data_driver =
data_driver::module(&imports, &type_map, &functions, &events, &custom_handlers);

// Rebuild the module with stripped contract attributes on methods
let mod_vis = &module.vis;
Expand Down
35 changes: 23 additions & 12 deletions contract-macro/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ fn build_import_map(imports: &[ImportInfo]) -> HashMap<String, String> {
.collect()
}

/// Resolve a `syn::Type` to its fully qualified string form using the
/// contract module's imports.
///
/// Shared with the handler-signature validator so the validator and
/// `build_type_map` agree on what "resolved" means — if short-path handlers
/// compile end-to-end, they also match the canonical expected signatures.
pub(crate) fn resolve_type(ty: &syn::Type, imports: &[ImportInfo]) -> String {
let import_map = build_import_map(imports);
resolve_syn_type(ty, &import_map)
}

/// Resolve a type path to its fully qualified form.
///
/// Given a type like `Deposit` or `events::PauseToggled` and an import map,
Expand All @@ -38,7 +49,7 @@ fn build_import_map(imports: &[ImportInfo]) -> HashMap<String, String> {
/// - Multi-segment paths: `events::PauseToggled` ->
/// `my_crate::events::PauseToggled`
/// - Generic types: `Option<Deposit>` -> `Option<my_crate::Deposit>`
fn resolve_type(ty: &TokenStream2, import_map: &HashMap<String, String>) -> String {
fn resolve_type_tokens(ty: &TokenStream2, import_map: &HashMap<String, String>) -> String {
let ty_str = ty.to_string();

// Handle unit type
Expand Down Expand Up @@ -190,25 +201,25 @@ pub(crate) fn build_type_map(
// Resolve function input, output, and feed types
for func in functions {
let input_key = func.input_type.to_string();
let input_resolved = resolve_type(&func.input_type, &import_map);
let input_resolved = resolve_type_tokens(&func.input_type, &import_map);
type_map.insert(input_key, input_resolved);

let output_key = func.output_type.to_string();
let output_resolved = resolve_type(&func.output_type, &import_map);
let output_resolved = resolve_type_tokens(&func.output_type, &import_map);
type_map.insert(output_key, output_resolved);

// Resolve feed_type if present (from #[contract(feeds = "Type")])
if let Some(feed_type) = &func.feed_type {
let feed_key = feed_type.to_string();
let feed_resolved = resolve_type(feed_type, &import_map);
let feed_resolved = resolve_type_tokens(feed_type, &import_map);
type_map.insert(feed_key, feed_resolved);
}
}

// Resolve event data types and topic paths
for event in events {
let data_key = event.data_type.to_string();
let data_resolved = resolve_type(&event.data_type, &import_map);
let data_resolved = resolve_type_tokens(&event.data_type, &import_map);
type_map.insert(data_key, data_resolved);

// Also resolve the topic path (e.g., "events::PauseToggled::PAUSED")
Expand Down Expand Up @@ -238,7 +249,7 @@ mod tests {
let import_map = build_import_map(&imports);

let ty = quote! { Deposit };
let resolved = resolve_type(&ty, &import_map);
let resolved = resolve_type_tokens(&ty, &import_map);
assert_eq!(resolved, "my_crate::Deposit");
}

Expand All @@ -248,7 +259,7 @@ mod tests {
let import_map = build_import_map(&imports);

let ty = quote! { DSAddress };
let resolved = resolve_type(&ty, &import_map);
let resolved = resolve_type_tokens(&ty, &import_map);
assert_eq!(resolved, "my_crate::Address");
}

Expand All @@ -258,7 +269,7 @@ mod tests {
let import_map = build_import_map(&imports);

let ty = quote! { events::PauseToggled };
let resolved = resolve_type(&ty, &import_map);
let resolved = resolve_type_tokens(&ty, &import_map);
assert_eq!(resolved, "my_crate::events::PauseToggled");
}

Expand All @@ -268,7 +279,7 @@ mod tests {
let import_map = build_import_map(&imports);

let ty = quote! { Option<Deposit> };
let resolved = resolve_type(&ty, &import_map);
let resolved = resolve_type_tokens(&ty, &import_map);
assert_eq!(resolved, "Option<my_crate::Deposit>");
}

Expand All @@ -281,7 +292,7 @@ mod tests {
let import_map = build_import_map(&imports);

let ty = quote! { (Deposit, DSAddress) };
let resolved = resolve_type(&ty, &import_map);
let resolved = resolve_type_tokens(&ty, &import_map);
assert_eq!(resolved, "(my_crate::Deposit, my_crate::Address)");
}

Expand All @@ -291,7 +302,7 @@ mod tests {
let import_map = build_import_map(&imports);

let ty = quote! { () };
let resolved = resolve_type(&ty, &import_map);
let resolved = resolve_type_tokens(&ty, &import_map);
assert_eq!(resolved, "()");
}

Expand All @@ -301,7 +312,7 @@ mod tests {
let import_map = build_import_map(&imports);

let ty = quote! { u64 };
let resolved = resolve_type(&ty, &import_map);
let resolved = resolve_type_tokens(&ty, &import_map);
assert_eq!(resolved, "u64");
}
}
Loading