fluentbase_sdk_derive/lib.rs
1//! Procedural macros for routers, clients, events, and storage layouts in Fluentbase contracts.
2use fluentbase_sdk_derive_core::{
3 client, event, router, storage::process_storage_layout, storage_legacy,
4};
5use proc_macro::TokenStream;
6use proc_macro_error::proc_macro_error;
7use quote::{quote, ToTokens};
8use syn::parse_macro_input;
9
10mod utils;
11
12/// Function ID attribute for overriding function selectors in smart contracts.
13///
14/// Specifies a custom 4-byte selector that identifies function calls in the ABI.
15///
16/// # Formats
17/// - Solidity signature: `#[function_id("transfer(address,uint256)")]`
18/// - Hex string: `#[function_id("0xa9059cbb")]`
19/// - Byte array: `#[function_id([169, 5, 156, 187])]`
20///
21/// # Validation
22/// Optional validation ensures the selector matches the function signature:
23///
24/// ```rust,ignore
25/// // Verify hex selector matches the function signature
26/// #[function_id("0xa9059cbb", validate(true))]
27/// fn transfer(&self, to: Address, amount: U256) -> bool { ... }
28///
29/// // Verify function implementation matches the signature
30/// #[function_id("transfer(address,uint256)", validate(true))]
31/// fn transfer(&self, to: Address, amount: U256) -> bool { ... }
32/// ```
33///
34/// Validation is useful when:
35/// - Ensuring type conversions produce the expected selector
36/// - Maintaining compatibility with existing contracts
37/// - During code refactoring to catch selector changes
38///
39/// By default, validation is disabled and selectors are used as-is.
40///
41/// # Example
42/// ```rust,ignore
43/// #[function_id("greeting(string)")]
44/// fn greeting(&self, message: String) -> String {
45/// message
46/// }
47/// ```
48#[proc_macro_attribute]
49pub fn function_id(_attr: TokenStream, item: TokenStream) -> TokenStream {
50 item
51}
52/// Router macro for Fluentbase smart contracts.
53///
54/// Automatically creates a method dispatch system that routes incoming function calls
55/// based on their 4-byte selectors, handling parameter decoding and result encoding.
56///
57/// # Usage
58///
59/// ## Trait Implementation
60///
61/// ```rust,ignore
62/// #[router(mode = "solidity")]
63/// impl<SDK: SharedAPI> ContractTrait for Contract<SDK> {
64/// #[function_id("greeting(string)")]
65/// fn greeting(&self, message: String) -> String {
66/// message
67/// }
68/// }
69/// ```
70///
71/// ## Direct Implementation
72///
73/// ```rust,ignore
74/// #[router(mode = "solidity")]
75/// impl<SDK: SharedAPI> Contract<SDK> {
76/// // Only public methods are included in routing
77/// pub fn store(&mut self, value: U256) {
78/// // Implementation
79/// }
80///
81/// // Private methods are excluded from routing
82/// fn internal_helper(&self) {
83/// // Implementation
84/// }
85/// }
86/// ```
87///
88/// # Special Methods
89///
90/// - **deploy**: Always excluded from routing, used for initialization ```rust,ignore fn
91/// deploy(&self) { // Deployment logic, called only once } ```
92///
93/// - **fallback**: Handles unmatched selectors ```rust,ignore fn fallback(&self) { // Called for
94/// unknown function selectors } ```
95///
96/// # Attributes
97///
98/// - **mode**: Encoding mode
99/// - `"solidity"`: Full EVM compatibility
100/// - `"fluent"`: Optimized for WASM (smaller payloads)
101///
102/// # Function Selectors
103///
104/// Methods are automatically assigned selectors based on their signatures,
105/// or can use custom selectors with `#[function_id(...)]`.
106///
107/// Selector collisions are detected at compile time.
108#[proc_macro_attribute]
109#[proc_macro_error]
110pub fn router(attr: TokenStream, input: TokenStream) -> TokenStream {
111 match router::process_router(attr.into(), input.into()) {
112 Ok(router) => router.to_token_stream().into(),
113 Err(err) => err.to_compile_error().into(),
114 }
115}
116/// Client macro for type-safe interaction with Fluentbase contracts.
117///
118/// Generates a client struct and methods from a trait definition, handling contract
119/// calls, parameter encoding, and result decoding automatically.
120///
121/// # Usage
122///
123/// ```rust,ignore
124/// #[client(mode = "solidity")]
125/// trait TokenInterface {
126/// #[function_id("balanceOf(address)")]
127/// fn balance_of(&self, owner: Address) -> U256;
128///
129/// #[function_id("transfer(address,uint256)")]
130/// fn transfer(&mut self, to: Address, amount: U256) -> bool;
131/// }
132///
133/// // Using the generated client
134/// let mut client = TokenInterfaceClient::new(sdk);
135///
136/// // Call contract methods with standard parameters
137/// let balance = client.balance_of(
138/// token_address, // contract address
139/// U256::zero(), // value to send (none)
140/// 50000, // gas limit
141/// my_address // method-specific parameters
142/// );
143/// ```
144///
145/// # Generated Code
146///
147/// For a trait named `TokenInterface`, generates:
148///
149/// - `TokenInterfaceClient<SDK>` struct with a `new(sdk)` constructor
150/// - Method implementations that append common parameters: ```rust,ignore fn method_name( &mut
151/// self, contract_address: Address, // Target contract value: U256, // Native
152/// tokens to send gas_limit: u64, // Maximum gas ...original_parameters // From
153/// trait definition ) -> original_return_type ```
154///
155/// # Features
156///
157/// - **Automatic encoding/decoding** of parameters and return values
158/// - **Runtime safety checks** for insufficient funds or gas
159/// - **Compatible with router** when using the same encoding mode
160/// - **Preserves method signatures** from the trait definition
161///
162/// # Attributes
163///
164/// - **mode**: Encoding mode
165/// - `"solidity"`: Full EVM compatibility (default)
166/// - `"fluent"`: Optimized encoding for WASM
167#[proc_macro_attribute]
168#[proc_macro_error]
169pub fn client(attr: TokenStream, input: TokenStream) -> TokenStream {
170 let attr_ts = proc_macro2::TokenStream::from(attr);
171 let input_items = parse_macro_input!(input as syn::ItemTrait);
172
173 match client::process_client(attr_ts, input_items.to_token_stream()) {
174 Ok(client) => client.to_token_stream().into(),
175 Err(err) => err.to_compile_error().into(),
176 }
177}
178
179/// Implements Solidity-compatible storage in Fluentbase contracts.
180///
181/// Provides an efficient, type-safe storage system following Solidity's storage layout,
182/// with automatic slot assignment and optimized access methods.
183///
184/// # Usage
185///
186/// ```rust,ignore
187/// solidity_storage! {
188/// // Simple values
189/// Address Owner; // Slot 0
190/// bool Paused; // Slot 1
191///
192/// // Mappings
193/// mapping(Address => U256) Balance; // Slot 2
194/// mapping(Address => mapping(Address => U256)) Allowance; // Slot 3
195///
196/// // Arrays
197/// U256[] Values; // Slot 4
198/// }
199///
200/// // Using the generated storage
201/// impl<SDK: SharedAPI> Contract<SDK> {
202/// fn transfer(&mut self, to: Address, amount: U256) -> bool {
203/// let sender = self.sdk.caller();
204/// let sender_balance = Balance::get(&self.sdk, sender);
205///
206/// if sender_balance >= amount {
207/// Balance::set(&mut self.sdk, sender, sender_balance - amount);
208/// Balance::set(&mut self.sdk, to, Balance::get(&self.sdk, to) + amount);
209/// true
210/// } else {
211/// false
212/// }
213/// }
214/// }
215/// ```
216///
217/// # Features
218///
219/// - **Key calculation**: Automatically implements correct hashing for mappings and arrays
220/// - **Optimization**: Uses direct storage access for types ≤ 32 bytes
221/// - **Type safety**: Generates properly typed getters and setters
222/// - **Full type support**: Works with all Solidity types including nested structures
223///
224/// # Generated API
225///
226/// For each variable `Name`, generates:
227///
228/// - A struct with slot constants: `struct Name { ... }`
229/// - Getter: `Name::get(sdk, ...arguments)` → returns the stored value
230/// - Setter: `Name::set(sdk, ...arguments, new_value)` → updates the stored value
231///
232/// The arguments depend on the storage type:
233/// - Simple values: no arguments
234/// - Mappings: one argument per key level
235/// - Arrays: indices for each dimension
236#[proc_macro]
237#[proc_macro_error]
238pub fn solidity_storage(input: TokenStream) -> TokenStream {
239 let storage = parse_macro_input!(input as storage_legacy::Storage);
240 storage.to_token_stream().into()
241}
242
243/// Generates Rust traits from Solidity interfaces and contracts.
244///
245/// Automatically converts Solidity definitions to Rust, preserving function
246/// signatures and type mappings, for seamless integration with Rust contracts.
247///
248/// # Usage
249///
250/// From a file:
251/// ```rust,ignore
252/// // Import from a .sol file
253/// derive_solidity_trait!("abi/IERC20.sol");
254/// ```
255///
256/// Or inline:
257/// ```rust,ignore
258/// // Define directly in Rust
259/// derive_solidity_trait!(
260/// interface IERC20 {
261/// function balanceOf(address account) external view returns (uint256);
262/// function transfer(address to, uint256 amount) external returns (bool);
263/// }
264/// );
265///
266/// // Use with router
267/// #[router(mode = "solidity")]
268/// impl<SDK: SharedAPI> IERC20 for MyToken<SDK> {
269/// fn balance_of(&self, account: Address) -> U256 {
270/// // Implementation
271/// }
272///
273/// fn transfer(&mut self, to: Address, amount: U256) -> bool {
274/// // Implementation
275/// }
276/// }
277/// ```
278///
279/// # Features
280///
281/// - **Automatic type conversion**: Solidity → Rust types
282/// - **Name conversion**: camelCase → snake_case for methods
283/// - **Method receivers**: `&self` for view/pure, `&mut self` for others
284/// - **Struct support**: Generates Rust structs for Solidity structs
285/// - **Works with router**: Use traits in `#[router]` implementations
286#[proc_macro]
287#[proc_macro_error]
288pub fn derive_solidity_trait(input: TokenStream) -> TokenStream {
289 let parsed = syn::parse_macro_input!(input as alloy_sol_macro_input::SolInput);
290
291 fluentbase_sdk_derive_core::sol_input::to_rust_trait(parsed)
292 .unwrap_or_else(syn::Error::into_compile_error)
293 .into()
294}
295
296/// Generates ready-to-use client code from Solidity definitions.
297///
298/// Creates a complete client implementation that can interact with
299/// deployed contracts, combining `derive_solidity_trait` and `client`
300/// functionality in one step.
301///
302/// # Usage
303///
304/// ```rust,ignore
305/// // Generate client from Solidity interface
306/// derive_solidity_client!(
307/// interface IERC20 {
308/// function balanceOf(address account) external view returns (uint256);
309/// function transfer(address to, uint256 amount) external returns (bool);
310/// }
311/// );
312///
313/// // Use the generated client
314/// fn example<SDK: SharedAPI>(sdk: SDK) {
315/// let mut client = IERC20Client::new(sdk);
316///
317/// // Check balance
318/// let balance = client.balance_of(
319/// token_address, // contract to call
320/// U256::zero(), // no value to send
321/// 50000, // gas limit
322/// my_address // function parameter
323/// );
324///
325/// // Transfer tokens
326/// if balance > amount {
327/// let success = client.transfer(
328/// token_address, U256::zero(), 50000,
329/// recipient, amount
330/// );
331/// }
332/// }
333/// ```
334///
335/// # Features
336///
337/// - **One-step generation**: Creates both trait and client implementation
338/// - **Type-safe methods**: Enforces correct parameter and return types
339/// - **Standard parameters**: All methods include contract address, value, and gas limit
340/// - **Full Solidity support**: Works with structs, arrays, and complex types
341/// - **File loading**: Can import from `.sol` files with
342/// `derive_solidity_client!("path/to/file.sol")`
343#[proc_macro]
344#[proc_macro_error]
345pub fn derive_solidity_client(input: TokenStream) -> TokenStream {
346 let parsed = parse_macro_input!(input as alloy_sol_macro_input::SolInput);
347
348 fluentbase_sdk_derive_core::sol_input::to_sol_client(parsed)
349 .unwrap_or_else(syn::Error::into_compile_error)
350 .into()
351}
352
353/// Calculates a Keccak-256 function selector from a signature.
354///
355/// Returns the first 4 bytes of the Keccak-256 hash as a u32 value.
356///
357/// # Example
358///
359/// ```rust,ignore
360/// let selector = derive_keccak256_id!("transfer(address,uint256)");
361/// // Returns 0xa9059cbb
362/// ```
363#[proc_macro]
364pub fn derive_keccak256_id(token: TokenStream) -> TokenStream {
365 let signature = token.to_string();
366 let method_id = utils::calculate_keccak256_id(&signature);
367 TokenStream::from(quote! {
368 #method_id
369 })
370}
371
372/// Calculates a full Keccak-256 hash from a signature.
373///
374/// Returns the complete 32-byte Keccak-256 hash as a byte array.
375///
376/// # Example
377///
378/// ```rust,ignore
379/// let hash = derive_keccak256!("Transfer(address,address,uint256)");
380/// // Returns the complete hash as [u8; 32]
381/// ```
382#[proc_macro]
383pub fn derive_keccak256(token: TokenStream) -> TokenStream {
384 let signature = token.to_string();
385 let method_id = utils::calculate_keccak256(&signature);
386 TokenStream::from(quote! {
387 [#(#method_id,)*]
388 })
389}
390
391#[proc_macro]
392pub fn derive_keccak256_bytes4(token: TokenStream) -> TokenStream {
393 let signature = token.to_string();
394 let method_id = &utils::calculate_keccak256(&signature)[..4];
395 TokenStream::from(quote! {
396 [#(#method_id,)*]
397 })
398}
399
400#[proc_macro]
401pub fn derive_evm_error(token: TokenStream) -> TokenStream {
402 let signature = token.to_string();
403 let method_id = utils::calculate_keccak256_id(&signature);
404 TokenStream::from(quote! {
405 #method_id
406 })
407}
408
409fn derive_storage_layout(input: TokenStream) -> TokenStream {
410 let input = syn::parse(input).unwrap();
411 match process_storage_layout(input) {
412 Ok(tokens) => tokens.into(),
413 Err(err) => err.to_compile_error().into(),
414 }
415}
416
417/// Derives storage layout for nested structures.
418///
419/// Use for composite storage types within contracts.
420/// For main contracts, use `#[derive(Contract)]` instead.
421///
422/// # Field Attributes
423///
424/// ## `#[slot(expr)]`
425///
426/// Places field at explicit storage slot. The expression must evaluate to `U256`.
427///
428/// ```rust,ignore
429/// use fluentbase_sdk::derive::{Storage, eip1967_slot};
430///
431/// pub const MY_SLOT: U256 = eip1967_slot!("eip1967.proxy.implementation");
432///
433/// #[derive(Storage)]
434/// struct Config {
435/// #[slot(MY_SLOT)]
436/// implementation: StorageAddress,
437///
438/// // Auto-layout continues normally
439/// owner: StorageAddress,
440/// }
441/// ```
442///
443/// Fields with explicit slots:
444/// - Do not affect auto-layout counter
445/// - Are excluded from `SLOTS` constant
446/// - Must use `U256` type (compile error otherwise)
447///
448/// # Example
449/// ```rust,ignore
450/// #[derive(Storage)]
451/// struct Config {
452/// owner: StoragePrimitive<Address>,
453/// version: StoragePrimitive<u32>,
454/// }
455/// ```
456#[proc_macro_derive(Storage, attributes(slot))]
457pub fn derive_storage(input: TokenStream) -> TokenStream {
458 derive_storage_layout(input)
459}
460
461/// Derives contract implementation with storage support.
462///
463/// Generates initialization and storage accessor methods for contract structs.
464/// For nested storage structures, use `#[derive(Storage)]` instead.
465///
466/// # Field Attributes
467///
468/// ## `#[slot(expr)]`
469///
470/// Places field at explicit storage slot for EIP-1967 proxy patterns
471/// or ERC-7201 namespaced storage.
472///
473/// ```rust,ignore
474/// use fluentbase_sdk::derive::{Contract, eip1967_slot, erc7201_slot};
475///
476/// pub mod slots {
477/// use fluentbase_sdk::derive::eip1967_slot;
478/// pub const IMPLEMENTATION: U256 = eip1967_slot!("eip1967.proxy.implementation");
479/// }
480///
481/// #[derive(Contract)]
482/// pub struct Proxy<SDK> {
483/// sdk: SDK,
484///
485/// #[slot(slots::IMPLEMENTATION)]
486/// implementation: StorageAddress,
487///
488/// // Auto-layout fields
489/// owner: StorageAddress,
490/// counter: StorageU256,
491/// }
492/// ```
493///
494/// # Helper Macros
495///
496/// - [`eip1967_slot!`] - Computes EIP-1967 slot: `keccak256(id) - 1`
497/// - [`erc7201_slot!`] - Computes ERC-7201 slot: `keccak256(keccak256(id) - 1) & ~0xff`
498///
499/// # Example
500/// ```rust,ignore
501/// #[derive(Contract)]
502/// pub struct MyToken<SDK> {
503/// sdk: SDK,
504/// total_supply: StorageU256,
505/// balances: StorageMap<Address, U256>,
506/// }
507/// ```
508#[proc_macro_derive(Contract, attributes(slot))]
509pub fn derive_contract(input: TokenStream) -> TokenStream {
510 derive_storage_layout(input)
511}
512
513/// Computes EIP-1967 storage slot at compile time.
514/// Formula: keccak256(id) - 1
515///
516/// See: <https://eips.ethereum.org/EIPS/eip-1967>
517///
518/// # Example
519/// ```rust,ignore
520/// use fluentbase_sdk::derive::eip1967_slot;
521///
522/// const IMPL_SLOT: U256 = eip1967_slot!("eip1967.proxy.implementation");
523/// ```
524#[proc_macro]
525pub fn eip1967_slot(input: TokenStream) -> TokenStream {
526 let lit = syn::parse_macro_input!(input as syn::LitStr);
527 let id = lit.value();
528
529 let hash = utils::calculate_keccak256(&id);
530
531 // keccak256(id) - 1
532 let mut bytes = hash;
533 let mut borrow = true;
534 for i in (0..32).rev() {
535 if borrow {
536 if bytes[i] == 0 {
537 bytes[i] = 0xff;
538 } else {
539 bytes[i] -= 1;
540 borrow = false;
541 }
542 }
543 }
544
545 let sdk_crate_path = get_sdk_crate_path();
546 TokenStream::from(quote! {
547 #sdk_crate_path::U256::from_be_bytes([#(#bytes),*])
548 })
549}
550
551/// Computes ERC-7201 namespaced storage slot at compile time.
552/// Formula: keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~0xff
553///
554/// See: <https://eips.ethereum.org/EIPS/eip-7201>
555///
556/// # Example
557/// ```rust,ignore
558/// use fluentbase_sdk::derive::erc7201_slot;
559///
560/// const STORAGE_SLOT: U256 = erc7201_slot!("example.main");
561/// ```
562#[proc_macro]
563pub fn erc7201_slot(input: TokenStream) -> TokenStream {
564 let lit = syn::parse_macro_input!(input as syn::LitStr);
565 let id = lit.value();
566
567 // Step 1: keccak256(id)
568 let inner = utils::calculate_keccak256(&id);
569
570 // Step 2: subtract 1
571 let mut shifted = inner;
572 let mut borrow = true;
573 for i in (0..32).rev() {
574 if borrow {
575 if shifted[i] == 0 {
576 shifted[i] = 0xff;
577 } else {
578 shifted[i] -= 1;
579 borrow = false;
580 }
581 }
582 }
583
584 // Step 3: keccak256(shifted)
585 let mut outer = utils::calculate_keccak256_raw::<32>(&shifted);
586
587 // Step 4: & ~0xff (clear last byte)
588 outer[31] = 0;
589
590 let sdk_crate_path = get_sdk_crate_path();
591 TokenStream::from(quote! {
592 #sdk_crate_path::U256::from_be_bytes([#(#outer),*])
593 })
594}
595
596/// Detect the crate path for the codec library
597fn get_sdk_crate_path() -> proc_macro2::TokenStream {
598 let crate_name = std::env::var("CARGO_PKG_NAME").unwrap_or_default();
599 if crate_name == "fluentbase-sdk" {
600 quote! { ::fluentbase_types }
601 } else {
602 quote! { ::fluentbase_sdk }
603 }
604}
605
606/// Defines contract initialization logic.
607///
608/// Generates a `deploy()` entry point that handles parameter decoding
609/// during contract deployment. Use when implementing traits or when
610/// you need initialization separate from runtime methods.
611///
612/// # Example
613/// ```rust,ignore
614/// #[constructor(mode = "solidity")]
615/// impl<SDK: SharedAPI> MyContract<SDK> {
616/// pub fn constructor(&mut self, owner: Address, supply: U256) {
617/// // Initialization logic
618/// }
619/// }
620/// ```
621///
622/// # Attributes
623/// - `mode`: `"solidity"` (EVM) or `"fluent"` (optimized)
624#[proc_macro_attribute]
625#[proc_macro_error]
626pub fn constructor(attr: TokenStream, input: TokenStream) -> TokenStream {
627 match fluentbase_sdk_derive_core::constructor::process_constructor(attr.into(), input.into()) {
628 Ok(constructor) => constructor.to_token_stream().into(),
629 Err(err) => err.to_compile_error().into(),
630 }
631}
632
633/// Derives Solidity-compatible event emission for structs.
634///
635/// # Example
636/// ```rust,ignore
637/// #[derive(Event)]
638/// struct Transfer {
639/// #[indexed]
640/// from: Address,
641/// #[indexed]
642/// to: Address,
643/// value: U256,
644/// }
645///
646/// Transfer { from, to, value }.emit(&mut sdk);
647/// ```
648#[proc_macro_derive(Event, attributes(indexed, anonymous))]
649#[proc_macro_error]
650pub fn derive_event(input: TokenStream) -> TokenStream {
651 let input = parse_macro_input!(input as syn::DeriveInput);
652 match event::process_event(input) {
653 Ok(tokens) => tokens.into(),
654 Err(err) => err.to_compile_error().into(),
655 }
656}