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
232 changes: 232 additions & 0 deletions crates/sdk-derive/derive-core/src/constructor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
use crate::{
attr::mode::Mode,
codec::CodecGenerator,
method::{MethodCollector, ParsedMethod},
};
use darling::{ast::NestedMeta, FromMeta};
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_error::{abort, abort_call_site, emit_error};
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, visit, Error, ImplItemFn, ItemImpl, Result};

/// Attributes for the constructor configuration.
#[derive(Debug, FromMeta, Default, Clone)]
pub struct ConstructorAttributes {
/// The encoding mode (Solidity or Fluent)
pub mode: Mode,
}

/// A constructor macro that generates deployment logic for smart contracts.
/// Handles parameter decoding and initialization during contract deployment.
#[derive(Debug)]
pub struct Constructor {
/// Constructor configuration attributes
attributes: ConstructorAttributes,
/// The original implementation block
impl_block: ItemImpl,
/// The parsed constructor method
constructor_method: ParsedMethod<ImplItemFn>,
}

/// Parses and validates a constructor from token streams.
pub fn process_constructor(attr: TokenStream2, input: TokenStream2) -> Result<Constructor> {
let attributes = parse_attributes(attr)?;
let impl_block = syn::parse2::<ItemImpl>(input)?;

Constructor::new(attributes, impl_block)
}

/// Parses constructor attributes from a TokenStream.
fn parse_attributes(attr: TokenStream2) -> Result<ConstructorAttributes> {
let meta = NestedMeta::parse_meta_list(attr)?;
ConstructorAttributes::from_list(&meta)
.map_err(|e| Error::new(Span::call_site(), e.to_string()))
}

impl Constructor {
/// Creates a new Constructor instance by parsing the implementation block.
pub fn new(attributes: ConstructorAttributes, impl_block: ItemImpl) -> Result<Self> {
// Use the existing MethodCollector to find the constructor
let is_trait_impl = impl_block.trait_.is_some();
let mut collector =
MethodCollector::<ImplItemFn>::new_for_impl(impl_block.span(), is_trait_impl);

visit::visit_item_impl(&mut collector, &impl_block);

// Validate we have exactly one constructor
if collector.constructor.is_none() {
abort!(
impl_block.span(),
"No constructor method found in implementation block";
help = "Add a method named 'constructor' to initialize the contract";
help = "Example: pub fn constructor(&mut self, initial_value: U256) {{ ... }}"
);
}

// Check for any errors during collection
if collector.has_errors() {
for err in &collector.errors {
emit_error!(err.span(), "{}", err.to_string());
}
abort_call_site!("Failed to process constructor due to parsing errors");
}

// Warn if regular methods were found (they'll be ignored)
if !collector.methods.is_empty() {
let method_names: Vec<String> = collector
.methods
.iter()
.map(|m| m.parsed_signature().rust_name())
.collect();

emit_error!(
impl_block.span(),
"Found {} non-constructor methods that will be ignored: {}",
collector.methods.len(),
method_names.join(", ");
note = "The #[constructor] macro only processes the 'constructor' method";
help = "Use #[router] macro if you need to handle other methods"
);
}

let constructor_method = collector
.constructor
.ok_or_else(|| Error::new(impl_block.span(), "Constructor method not found"))?;

Ok(Self {
attributes,
impl_block,
constructor_method,
})
}

/// Returns the constructor attributes.
pub fn attributes(&self) -> &ConstructorAttributes {
&self.attributes
}

/// Returns the implementation block.
pub fn impl_block(&self) -> &ItemImpl {
&self.impl_block
}

/// Returns the constructor method.
pub fn constructor_method(&self) -> &ParsedMethod<ImplItemFn> {
&self.constructor_method
}

/// Generates the complete constructor implementation with all artifacts.
pub fn generate(&self) -> Result<TokenStream2> {
// Generate codec for constructor parameters
let constructor_codec = self.generate_constructor_codec()?;

// Generate deploy method
let deploy_method = self.generate_deploy_method()?;

// Keep the original impl block
let impl_block = &self.impl_block;

// Build the complete output
Ok(quote! {
#impl_block

#constructor_codec

#deploy_method
})
}

/// Generates codec implementation for constructor parameters.
fn generate_constructor_codec(&self) -> Result<TokenStream2> {
CodecGenerator::new(&self.constructor_method, &self.attributes.mode)
.generate()
.map_err(|e| {
Error::new(
self.constructor_method.parsed_signature().span(),
e.to_string(),
)
})
}

/// Generates the deploy method implementation.
fn generate_deploy_method(&self) -> Result<TokenStream2> {
let target_type = &self.impl_block.self_ty;
let generic_params = &self.impl_block.generics;

let deploy_body = self.constructor_method.generate_deploy_body();

Ok(quote! {
impl #generic_params #target_type {
/// Deploy entry point for contract initialization.
/// This method is called once during contract deployment.
pub fn deploy(&mut self) {
#deploy_body
}
}
})
}
}

impl ToTokens for Constructor {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self.generate() {
Ok(generated) => tokens.extend(generated),
Err(e) => tokens.extend(Error::to_compile_error(&e)),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
use syn::parse_quote;

#[test]
fn test_constructor_no_params() {
let impl_block: ItemImpl = parse_quote! {
impl<SDK: SharedAPI> MyContract<SDK> {
pub fn constructor(&mut self) {
// Initialize without parameters
}
}
};

let attr_tokens = quote! { mode = "solidity" };
let constructor = process_constructor(attr_tokens, impl_block.into_token_stream())
.expect("Failed to process constructor");

let generated = constructor
.generate()
.expect("Failed to generate constructor code");

let file = syn::parse_file(&generated.to_string()).unwrap();
let formatted = prettyplease::unparse(&file);

assert_snapshot!("constructor_no_params", formatted);
}

#[test]
fn test_constructor_with_params() {
let impl_block: ItemImpl = parse_quote! {
impl<SDK: SharedAPI> Token<SDK> {
pub fn constructor(&mut self, owner: Address, initial_supply: U256) {
// Initialize with parameters
}
}
};

let attr_tokens = quote! { mode = "solidity" };
let constructor = process_constructor(attr_tokens, impl_block.into_token_stream())
.expect("Failed to process constructor");

let generated = constructor
.generate()
.expect("Failed to generate constructor code");

let file = syn::parse_file(&generated.to_string()).unwrap();
let formatted = prettyplease::unparse(&file);

assert_snapshot!("constructor_with_params", formatted);
}
}
1 change: 1 addition & 0 deletions crates/sdk-derive/derive-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod abi;
pub mod attr;
pub mod client;
mod codec;
pub mod constructor;
mod method;
pub mod router;
mod signature;
Expand Down
106 changes: 87 additions & 19 deletions crates/sdk-derive/derive-core/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use crate::{
attr::{function_id::FunctionID, FunctionIDAttribute},
signature::ParsedSignature,
};
use proc_macro2::Span;

use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_error::{abort, abort_call_site};
use quote::{format_ident, quote};
use std::collections::HashSet;
use syn::{
spanned::Spanned,
Expand Down Expand Up @@ -201,6 +203,72 @@ impl<T: MethodLike> ParsedMethod<T> {
pub fn is_constructor(&self) -> bool {
self.sig.rust_name() == CONSTRUCTOR_METHOD
}
// В method.rs, добавить в impl<T: MethodLike> ParsedMethod<T>

/// Generates the deploy method body for a constructor.
/// This includes parameter decoding and constructor call logic.
pub fn generate_deploy_body(&self) -> TokenStream2 {
assert!(
self.is_constructor(),
"generate_deploy_body should only be called for constructor methods"
);

let fn_name = format_ident!("constructor");
let params = self.parsed_signature().parameters();
let param_count = params.len();

let call_struct = format_ident!("ConstructorCall");

// Generate parameter handling based on parameter count
let param_handling = match param_count {
0 => quote! {},
1 => quote! {
let param0 = match #call_struct::decode(&&call_data[..]) {
Ok(decoded) => decoded.0.0,
Err(err) => {
panic!("Failed to decode constructor parameters: {:?}", err);
}
};
},
_ => {
let param_names = (0..param_count)
.map(|i| format_ident!("param{}", i))
.collect::<Vec<_>>();
let param_indices = (0..param_count).map(syn::Index::from).collect::<Vec<_>>();

quote! {
let (#(#param_names),*) = match #call_struct::decode(&&call_data[..]) {
Ok(decoded) => (#(decoded.0.#param_indices),*),
Err(err) => {
panic!("Failed to decode constructor parameters: {:?}", err);
}
};
}
}
};

// Generate function call based on parameter count
let fn_call = match param_count {
0 => quote! { self.#fn_name() },
1 => quote! { self.#fn_name(param0) },
_ => {
let param_names = (0..param_count)
.map(|i| format_ident!("param{}", i))
.collect::<Vec<_>>();
quote! { self.#fn_name(#(#param_names),*) }
}
};

// Generate complete deploy body
quote! {
let input_length = self.sdk.input_size();
let mut call_data = ::fluentbase_sdk::alloc_slice(input_length as usize);
self.sdk.read(&mut call_data, 0);

#param_handling
#fn_call;
}
}
}

/// Collector for gathering methods from trait or impl blocks
Expand Down Expand Up @@ -261,13 +329,13 @@ impl<T: MethodLike> MethodCollector<T> {

if count > 1 {
return Err(Error::new(
method.parsed_signature().span(),
format!(
"Function selector collision detected for '{}'. Selector: {:02x}{:02x}{:02x}{:02x}",
method.parsed_signature().rust_name(),
selector[0], selector[1], selector[2], selector[3]
),
));
method.parsed_signature().span(),
format!(
"Function selector collision detected for '{}'. Selector: {:02x}{:02x}{:02x}{:02x}",
method.parsed_signature().rust_name(),
selector[0], selector[1], selector[2], selector[3]
),
));
}
}

Expand All @@ -284,13 +352,13 @@ impl<T: MethodLike> MethodCollector<T> {
let selector = method.function_id();
if !self.selectors.insert(selector) {
self.add_error(
method.parsed_signature().span(),
format!(
"Function selector collision detected for '{}'. Selector: {:02x}{:02x}{:02x}{:02x}",
method.parsed_signature().rust_name(),
selector[0], selector[1], selector[2], selector[3]
),
);
method.parsed_signature().span(),
format!(
"Function selector collision detected for '{}'. Selector: {:02x}{:02x}{:02x}{:02x}",
method.parsed_signature().rust_name(),
selector[0], selector[1], selector[2], selector[3]
),
);
} else {
self.methods.push(method);
}
Expand Down Expand Up @@ -320,10 +388,10 @@ impl<T: MethodLike> MethodCollector<T> {
fn handle_fallback_method(&mut self, method_sig: &Signature, span: Span) {
if !self.validate_fallback_signature(method_sig) {
self.add_error(
span,
format!("{} method must have signature 'fn {}(&self)' with no parameters and no return value",
FALLBACK_METHOD, FALLBACK_METHOD),
);
span,
format!("{} method must have signature 'fn {}(&self)' with no parameters and no return value",
FALLBACK_METHOD, FALLBACK_METHOD),
);
}
}

Expand Down
Loading
Loading