sylvia_derive/lib.rs
1//! This crate defines [Sylvia](https://docs.rs/sylvia/latest/sylvia) procedural macros.
2//!
3//! Please refer to the [Sylvia-book](https://cosmwasm.github.io/sylvia-book/index.html) on how to use these macros.
4
5use crate::parser::EntryPointArgs;
6use contract::ContractInput;
7use entry_points::EntryPointInput;
8use fold::StripInput;
9use interface::InterfaceInput;
10use proc_macro::TokenStream;
11use proc_macro2::TokenStream as TokenStream2;
12use proc_macro_error::proc_macro_error;
13use quote::quote;
14use syn::fold::Fold;
15use syn::spanned::Spanned;
16use syn::{parse2, parse_quote, ItemImpl, ItemTrait, Path};
17
18mod contract;
19mod entry_points;
20mod fold;
21mod interface;
22mod parser;
23mod types;
24mod utils;
25
26#[cfg(not(test))]
27pub(crate) fn crate_module() -> Path {
28 use proc_macro_crate::{crate_name, FoundCrate};
29
30 match crate_name("sylvia").expect("sylvia is not found in Cargo.toml") {
31 FoundCrate::Itself => parse_quote!(sylvia),
32 FoundCrate::Name(name) => {
33 let ident = syn::Ident::new(&name, proc_macro2::Span::mixed_site());
34 parse_quote!(#ident)
35 }
36 }
37}
38
39#[cfg(test)]
40pub(crate) fn crate_module() -> Path {
41 parse_quote!(sylvia)
42}
43
44/// Procedural macro generating messages from a contract trait.
45/// Generates `sudo`, `exec` and `query` enum messages to be later used in contract implementation.
46///
47/// ## Example usage
48///
49/// ```rust
50/// # use sylvia::cw_schema::cw_serde;
51/// # use sylvia::cw_std::{Response, StdError};
52/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
53/// #
54/// # #[cw_serde]
55/// # pub struct AdminQueryResponse;
56/// #
57/// ##[sylvia::interface]
58/// pub trait SvInterface {
59/// type Error: From<StdError>;
60///
61/// #[sv::msg(exec)]
62/// fn update_admin(&self, ctx: ExecCtx, admin: Option<String>) -> Result<Response, Self::Error>;
63///
64/// #[sv::msg(query)]
65/// fn admin(&self, ctx: QueryCtx) -> Result<AdminQueryResponse, Self::Error>;
66///
67/// #[sv::msg(sudo)]
68/// fn remove_admin(&self, ctx: SudoCtx, #[serde(default)] admin: String) -> Result<Response, Self::Error>;
69/// }
70/// # fn main() {}
71/// ```
72///
73/// This would generate output like:
74///
75/// ```rust
76/// # use sylvia::cw_schema::cw_serde;
77/// # use sylvia::cw_std::{Response, StdError, DepsMut, Env, MessageInfo};
78/// # use sylvia::ctx::ExecCtx;
79/// #
80/// # pub trait SvInterface {
81/// # type Error: From<StdError>;
82/// # }
83/// #
84/// pub mod sv {
85/// # use super::*;
86/// #
87/// #[derive(
88/// sylvia::serde::Serialize,
89/// sylvia::serde::Deserialize,
90/// Clone,
91/// Debug,
92/// PartialEq,
93/// sylvia::schemars::JsonSchema,
94/// )]
95/// #[serde(rename_all = "snake_case")]
96/// pub enum ExecMsg {
97/// UpdateAdmin { admin: Option<String> },
98/// }
99///
100/// impl ExecMsg {
101/// pub fn dispatch<C: SvInterface>(contract: &C, ctx: (DepsMut, Env, MessageInfo))
102/// -> Result<Response, C::Error>
103/// {
104/// // Some dispatching implementation
105/// # Ok(Response::new())
106/// }
107/// }
108/// }
109/// # fn main() {}
110/// ```
111///
112/// Similarly for `Query` and `Sudo` enum messages.
113///
114/// ## Associated types
115///
116/// Generics are not supported by the `interface` macro and won't be. Instead, you can define
117/// associated types on an interface to allow users implementing it to customize the behavior to their liking.
118///
119/// Some names are however parsed and used in special contexts. Those are:
120/// * `Error` - error type returned by interface methods. This one is required to be declared.
121/// * `ExecC` - custom message type used in messages. Has to implement `cosmwasm_std::CustomMsg`.
122/// * `QueryC` - custom query type used in messages. Has to implement `cosmwasm_std::CustomQuery`.
123///
124/// ```rust
125/// # use sylvia::cw_schema::cw_serde;
126/// # use sylvia::cw_std::{CustomMsg, CustomQuery, Response, StdError};
127/// # use sylvia::ctx::ExecCtx;
128/// #
129/// ##[sylvia::interface]
130/// pub trait SvInterface {
131/// type Error: From<StdError>;
132/// type ExecC: CustomMsg;
133/// type QueryC: CustomQuery;
134///
135/// #[sv::msg(exec)]
136/// fn update_admin(&self, ctx: ExecCtx<Self::QueryC>, admin: Option<String>) -> Result<Response<Self::ExecC>, Self::Error>;
137/// }
138/// # fn main() {}
139/// ```
140///
141/// Although it's not required to define `ExecC` and `QueryC` types, it's recommended to do so to allow
142/// the users of the interface to customize the behavior to their liking.
143///
144/// If however you want to restrict the interface to use specific custom message and query types,
145/// you can do so using `#[sv::custom(msg=..., query=...)]` attribute explained below.
146///
147/// ## Attributes
148///
149/// `Interface` macro supports multiple attributes to customize the behavior of generated messages.
150///
151/// ### `sv::msg(...)`
152///
153/// Messages structures are generated basing on interface trait method attributed with
154/// `#[sv::msg(msg_type)]`. Msg attribute takes as its first argument type of message it is
155/// supposed to handle:
156/// * `exec` - execute message variant
157/// * `query` - query message variant
158/// * `sudo` - sudo message variant
159///
160/// In the case of a query, it is possible to pass a second argument which is its `ResponseType`.
161/// This is required in case of aliased results wrapping their `ResponseType` to properly
162/// implement [QueryResponses](https://docs.rs/cosmwasm-schema/latest/cosmwasm_schema/trait.QueryResponses.html).
163///
164/// ```rust
165/// # use sylvia::cw_schema::cw_serde;
166/// # use sylvia::cw_std::{Response, StdError};
167/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
168/// #
169/// # #[cw_serde]
170/// # pub struct AdminQueryResponse;
171/// #
172/// pub type AdminResult<ErrorT> = Result<AdminQueryResponse, ErrorT>;
173///
174/// ##[sylvia::interface]
175/// pub trait SvInterface {
176/// type Error: From<StdError>;
177///
178/// #[sv::msg(exec)]
179/// fn update_admin(&self, ctx: ExecCtx, admin: Option<String>) -> Result<Response, Self::Error>;
180///
181/// #[sv::msg(query, resp=AdminQueryResponse)]
182/// fn admin(&self, ctx: QueryCtx) -> AdminResult<Self::Error>;
183///
184/// #[sv::msg(sudo)]
185/// fn remove_admin(&self, ctx: SudoCtx, admin: String) -> Result<Response, Self::Error>;
186/// }
187/// # fn main() {}
188/// ```
189///
190/// ### `sv::custom(msg=..., query=...)`
191///
192/// Allows restricting interface to use specific
193/// custom message and query types. If used with `ExecC` and `QueryC` associated
194/// types `sv::custom(...)` attribute has priority in defining custom types.
195///
196/// ```rust
197/// # use sylvia::cw_schema::cw_serde;
198/// # use sylvia::cw_std::{CustomMsg, CustomQuery, Response, StdError};
199/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
200/// #
201/// ##[cw_serde]
202/// pub enum SvCustomMsg {}
203///
204/// ##[cw_serde]
205/// pub enum SvCustomQuery {}
206///
207/// impl CustomMsg for SvCustomMsg {}
208/// impl CustomQuery for SvCustomQuery {}
209///
210/// ##[sylvia::interface]
211/// ##[sv::custom(msg=SvCustomMsg, query=SvCustomQuery)]
212/// pub trait SvInterface {
213/// type Error: From<StdError>;
214/// type ExecC: CustomMsg;
215/// type QueryC: CustomQuery;
216///
217/// #[sv::msg(exec)]
218/// fn update_admin(&self, ctx: ExecCtx<SvCustomQuery>, admin: Option<String>) -> Result<Response<SvCustomMsg>, Self::Error>;
219/// }
220/// # fn main() {}
221/// ```
222///
223/// ### `sv::msg_attr(msg_type, {...})`
224///
225/// This attribute can be used for the whole `trait Interface {}` block and
226/// for the following message types: `exec`, `query` and `sudo`.
227/// The `{...}` part will be forwarded as an attribute `#[{...}]` to the
228/// given message type (enum or struct).
229///
230/// ### `sv::attr({...})`
231///
232/// Forwards variant's attribute to the specific enum's field in the
233/// generated message type. It can be used along with `sv::msg(...)`
234/// and only for message types variants that resolves in an enum field,
235/// i.e. `exec`, `query` and `sudo`.
236#[proc_macro_error]
237#[proc_macro_attribute]
238pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream {
239 interface_impl(attr.into(), item.into()).into()
240}
241
242fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
243 fn inner(item: TokenStream2) -> syn::Result<TokenStream2> {
244 let input: ItemTrait = parse2(item)?;
245
246 let expanded = InterfaceInput::new(&input).process();
247 let input = StripInput.fold_item_trait(input);
248
249 Ok(quote! {
250 #input
251
252 #expanded
253 })
254 }
255
256 inner(item).unwrap_or_else(syn::Error::into_compile_error)
257}
258
259/// Procedural macro generating messages from contract impl block.
260/// Generates `instantiate`, `migrate`, `reply`, `sudo`, `exec` and `query`
261/// enum messages to be later used in contract implementation.
262///
263/// ## Example usage
264/// ```rust
265/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
266/// # use sylvia::ctx::ReplyCtx;
267/// # use sylvia::cw_std::{Binary, Response, StdError, SubMsgResult};
268/// # use cw_storage_plus::Item;
269/// # use thiserror::Error;
270/// #
271/// # #[derive(Error, Debug, PartialEq)]
272/// # pub enum ContractError {
273/// # #[error("{0}")]
274/// # Std(#[from] StdError),
275/// # }
276/// # pub struct ContractData;
277/// #
278/// #
279/// pub struct SvContract {
280/// data: Item<ContractData>,
281/// }
282///
283/// ##[sylvia::contract]
284/// ##[sv::error(ContractError)]
285/// ##[sv::features(replies)]
286/// impl SvContract {
287/// pub const fn new() -> Self {
288/// Self {
289/// data: Item::new("data"),
290/// }
291/// }
292///
293/// #[sv::msg(instantiate)]
294/// fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
295/// # Ok(Response::new())
296/// }
297///
298/// #[sv::msg(exec)]
299/// fn execute(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
300/// # Ok(Response::new())
301/// }
302///
303/// #[sv::msg(query)]
304/// fn query(&self, ctx: QueryCtx) -> Result<Response, ContractError> {
305/// # Ok(Response::new())
306/// }
307///
308/// #[sv::msg(migrate)]
309/// fn migrate(&self, ctx: MigrateCtx) -> Result<Response, ContractError> {
310/// # Ok(Response::new())
311/// }
312///
313/// #[sv::msg(reply)]
314/// fn reply(&self, ctx: ReplyCtx, result: SubMsgResult, #[sv::payload(raw)] payload: Binary) -> Result<Response, ContractError> {
315/// # Ok(Response::new())
316/// }
317///
318/// #[sv::msg(sudo)]
319/// fn sudo(&self, ctx: SudoCtx) -> Result<Response, ContractError> {
320/// # Ok(Response::new())
321/// }
322/// }
323/// # fn main() {}
324/// ```
325///
326/// This would generate output like:
327///
328/// ```rust
329/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
330/// # use sylvia::ctx::ReplyCtx;
331/// # use sylvia::cw_std::{Binary, Response, StdError, SubMsgResult};
332/// # use cw_storage_plus::Item;
333/// # use thiserror::Error;
334/// #
335/// # #[derive(Error, Debug, PartialEq)]
336/// # pub enum ContractError {
337/// # #[error("{0}")]
338/// # Std(#[from] StdError),
339/// # }
340/// # pub struct ContractData;
341/// #
342/// # pub struct SvContract {
343/// # data: Item<ContractData>,
344/// # }
345/// #
346/// # impl SvContract {
347/// # pub const fn new() -> Self {
348/// # Self {
349/// # data: Item::new("data"),
350/// # }
351/// # }
352/// #
353/// # fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
354/// # Ok(Response::new())
355/// # }
356/// #
357/// # fn execute(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
358/// # Ok(Response::new())
359/// # }
360/// #
361/// # fn query(&self, ctx: QueryCtx) -> Result<Response, ContractError> {
362/// # Ok(Response::new())
363/// # }
364/// #
365/// # fn migrate(&self, ctx: MigrateCtx) -> Result<Response, ContractError> {
366/// # Ok(Response::new())
367/// # }
368/// #
369/// # fn reply(&self, ctx: ReplyCtx) -> Result<Response, ContractError> {
370/// # Ok(Response::new())
371/// # }
372/// #
373/// # fn sudo(&self, ctx: SudoCtx) -> Result<Response, ContractError> {
374/// # Ok(Response::new())
375/// # }
376/// # }
377/// #
378/// pub mod sv {
379/// # use super::*;
380/// #
381/// #[allow(clippy::derive_partial_eq_without_eq)]
382/// #[derive(
383/// sylvia::serde::Serialize,
384/// sylvia::serde::Deserialize,
385/// Clone,
386/// Debug,
387/// PartialEq,
388/// sylvia::schemars::JsonSchema,
389/// )]
390/// #[serde(rename_all = "snake_case")]
391/// pub struct InstantiateMsg {
392/// pub admin: Option<String>,
393/// }
394/// impl InstantiateMsg {
395/// pub fn new(admin: Option<String>) -> Self {
396/// Self { admin }
397/// }
398/// pub fn dispatch(
399/// self,
400/// contract: &SvContract,
401/// ctx: (
402/// sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
403/// sylvia::cw_std::Env,
404/// sylvia::cw_std::MessageInfo,
405/// ),
406/// ) -> Result<Response, ContractError> {
407/// let Self { admin } = self;
408/// contract
409/// .instantiate(Into::into(ctx), admin)
410/// .map_err(Into::into)
411/// }
412/// }
413/// }
414/// # fn main() {}
415/// ```
416///
417/// And appropriate messages for `exec`, `query`, `migrate`, `reply` and `sudo` variants.
418///
419/// ## Attributes
420///
421/// `Contract` macro supports multiple attributes to customize the behavior of generated messages.
422///
423/// ### `sv::msg(...)`
424///
425/// Message structures are generated based on specific implemented methods attributed with
426/// `#[sv::msg(msg_type)]`. Msg attribute takes as its first argument type of message it is
427/// supposed to handle:
428/// * `instantiate` - instantiation message handler. There should be always exactly one
429/// * `exec` - execute message variant
430/// * `query` - query message variant
431/// * `migrate` - migrate message variant
432/// * `reply` - reply message variant
433/// * `sudo` - sudo message variant
434///
435/// In the case of a query, it is possible to pass a second argument which is its `ResponseType`.
436/// This is required in case of aliased results wrapping their `ResponseType` to properly
437/// implement `QueryResponses`.
438///
439/// ```rust
440/// # use sylvia::ctx::{InstantiateCtx, QueryCtx};
441/// # use sylvia::cw_std::{Response, StdError};
442/// # use sylvia::cw_schema::cw_serde;
443/// # use cw_storage_plus::Item;
444/// # use thiserror::Error;
445/// #
446/// # #[derive(Error, Debug, PartialEq)]
447/// # pub enum ContractError {
448/// # #[error("{0}")]
449/// # Std(#[from] StdError),
450/// # }
451/// # pub struct ContractData;
452/// #
453/// # #[cw_serde]
454/// # pub struct QueryResponse;
455/// #
456/// pub struct SvContract {
457/// data: Item<ContractData>,
458/// }
459///
460/// ##[sylvia::contract]
461/// ##[sv::error(ContractError)]
462/// impl SvContract {
463/// pub const fn new() -> Self {
464/// Self {
465/// data: Item::new("data"),
466/// }
467/// }
468///
469/// #[sv::msg(instantiate)]
470/// fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
471/// Ok(Response::new())
472/// }
473///
474/// #[sv::msg(query, resp=QueryResponse)]
475/// fn query(&self, ctx: QueryCtx) -> Result<QueryResponse, ContractError> {
476/// // Some query implementation
477/// # Ok(QueryResponse)
478/// }
479/// }
480/// # fn main() {}
481/// ```
482///
483/// ### `sv::custom(msg=..., query=...)`
484///
485/// Allows restricting interface to use specific
486/// custom message and query types. If used with `ExecC` and `QueryC` associated
487/// types `sv::custom(...)` attribute has priority in defining custom types.
488///
489/// ### `sv::error(error_type)`
490///
491/// Allows specifing custom error type for the contract. Default is `cosmwasm_std::StdError`.
492///
493/// ### `sv::override_entry_point(entry_point_type=<path_to_entry_point(msg_path)>`
494///
495/// Allows overriding default entry point for specific message type.
496/// Used in case there is a need to provide some custom functionality inside the entry point.
497/// Specified entry point will be used in generated `multitest` helpers
498///
499/// ```rust
500/// # use sylvia::ctx::InstantiateCtx;
501/// # use sylvia::cw_std::{Response, StdResult, MessageInfo, DepsMut, Env, entry_point};
502/// # use sylvia::cw_schema::cw_serde;
503/// #
504/// pub struct SvContract;
505///
506/// #[cw_serde]
507/// pub enum CustomExecMsg {}
508///
509/// ##[entry_point]
510/// pub fn execute(
511/// deps: DepsMut,
512/// env: Env,
513/// info: MessageInfo,
514/// msg: CustomExecMsg,
515/// ) -> StdResult<Response> {
516/// # Ok(Response::new())
517/// }
518///
519///
520/// ##[sylvia::contract]
521/// ##[sv::override_entry_point(exec=execute(CustomExecMsg))]
522/// impl SvContract {
523/// # pub const fn new() -> Self {
524/// # Self
525/// # }
526/// #
527/// # #[sv::msg(instantiate)]
528/// # fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> StdResult<Response> {
529/// # Ok(Response::new())
530/// # }
531/// }
532/// # fn main() {}
533/// ```
534/// ### `sv::messages(path_to_interface)`
535///
536/// Used to declare interfaces implemented on the contract.
537/// Required for the contract to be able to handle an interface message.
538///
539/// ```rust
540/// # use sylvia::ctx::InstantiateCtx;
541/// # use sylvia::cw_std::{Response, StdError, StdResult};
542/// # use cw_storage_plus::Item;
543/// #
544/// pub mod sv_interface {
545/// # use sylvia::cw_std::StdError;
546/// #
547/// ##[sylvia::interface]
548/// pub trait SvInterface {
549/// type Error: From<StdError>;
550/// }
551/// }
552///
553/// impl sv_interface::SvInterface for SvContract {
554/// type Error = StdError;
555/// }
556///
557/// pub struct SvContract;
558///
559/// ##[sylvia::contract]
560/// ##[sv::messages(sv_interface)]
561/// impl SvContract {
562/// # pub const fn new() -> Self {
563/// # Self
564/// # }
565/// #
566/// # #[sv::msg(instantiate)]
567/// # fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
568/// # Ok(Response::new())
569/// # }
570/// }
571/// # fn main() {}
572/// ```
573///
574/// In case an interface has different name than a module in which its defined
575/// you have to pass the name of an interface as below:
576///
577/// ```rust
578/// # use sylvia::ctx::InstantiateCtx;
579/// # use sylvia::cw_std::{Response, StdError, StdResult};
580/// # use cw_storage_plus::Item;
581/// #
582/// pub mod interface {
583/// # use sylvia::cw_std::StdError;
584/// #
585/// ##[sylvia::interface]
586/// pub trait SvInterface {
587/// type Error: From<StdError>;
588/// }
589/// }
590///
591/// impl interface::SvInterface for SvContract {
592/// type Error = StdError;
593/// }
594///
595/// pub struct SvContract;
596///
597/// ##[sylvia::contract]
598/// ##[sv::messages(interface as SvInterface)]
599/// impl SvContract {
600/// # pub const fn new() -> Self {
601/// # Self
602/// # }
603/// #
604/// # #[sv::msg(instantiate)]
605/// # fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
606/// # Ok(Response::new())
607/// # }
608/// }
609/// # fn main() {}
610/// ```
611///
612/// ### `sv::msg_attr(msg_type, {...})`
613///
614/// This attribute can be used for the whole `impl Contract {}` block and
615/// for every message type: `exec`, `query`, `sudo`, `instantiate`,
616/// `migrate` and `reply`. The `{...}` part will be forwarded as an
617/// attribute `#[{...}]` to the given message type (enum or struct).
618///
619/// ### `sv::attr({...})`
620///
621/// Forwards variant's attribute to the specific enum's field in the
622/// generated message type. It can be used along with `sv::msg(...)`
623/// and only for message types variants that resolves in an enum field,
624/// i.e. `exec`, `query` and `sudo`.
625///
626/// ### `sv::features(...)`
627///
628/// Enables additional features for the contract. Allows user to use features that
629/// are considered breaking before the major release.
630///
631/// Currently supported features are:
632/// * `replies` - enables better dispatching of `reply` as well as its auto deserialization.
633/// With this feature enabled, user can use additional parameters in the
634/// `sv::msg` attribute like so:
635/// `#[sv::msg(reply, handlers=[scenario1, scenario2], reply_on=Success)]`.
636///
637/// Based on this parameters reply ids will be generated and associated with
638/// proper scenario specified by the `reply_on` parameter.
639///
640/// User can also specify custom `data` and `payload` types that will be auto
641/// deserialized from the `cosmwasm_std::Binary` type.
642///
643/// ### `sv::payload(raw)`
644///
645/// Requires contract to be marked with the `sv::features(replies)`.
646///
647/// Used next to the reply method argument. It disables auto deserialization
648/// of the payload argument.
649///
650/// ### `sv::data(...)`
651///
652/// Requires contract to be marked with the `sv::features(replies)`.
653///
654/// Used next to the reply method argument. Based on the passed parameters
655/// it enables different behavior:
656///
657/// * `#[sv::data(raw)]` - Returns error if the data is `None`, extracts and forwards
658/// `Binary` if it's `Some`.
659///
660/// * `#[sv::data(raw, opt)]` - Forwards `data` as `Option<Binary>`.
661///
662/// * `#[sv::data(opt)]` - Expects type of `data` in the method signature to be
663/// `data: Option<T> where T: Deserialize`.
664/// Tries to deserialize `data` to type defined in the method signature
665/// and forwards it wrapped in the `Option`.
666/// Requires `data` to be JSON serialized.
667///
668/// If `data` is:
669/// | `Some(valid)` | `Some(invalid)` | `None` |
670/// |---|---|---|
671/// | forwards `Some(valid)` | early returns an error specifying what went wrong with `serde` error attached | `None` - forwards `None` |
672///
673/// * `#[sv::data]` - Expects data in the method signature to be `data: T where T: Deserialize`.
674/// Tries to deserialize data to type defined in the method signature
675/// and forwards it or returns early an error.
676/// Requires `data` to be JSON serialized.
677///
678/// If `data` is:
679/// | `Some(valid)` | `Some(invalid)` | `None` |
680/// |---|---|---|
681/// | forwards `valid` | early returns error specifying what went wrong with `serde` error attached | early returns error specifying the `data` is missing |
682///
683/// * `#[sv::data(instantiate)]` - special case for reply to `WasmMsg::instantiate` and
684/// `WasmMsg::instantiate2`. Tries to deserialize data using
685/// [`parse_instantiate_response_data`](https://docs.rs/cw-utils/latest/cw_utils/fn.parse_instantiate_response_data.html).
686///
687/// If `data` is:
688/// | `Some(valid)` | `Some(invalid)` | `None` |
689/// |---|---|---|
690/// | extracts and forwards `valid` | early returns error specifying what went wrong with `serde` error attached | early returns error specifying the `data` is missing |
691///
692/// * `#[sv::data(instantiate, opt)]` - special case for reply to `WasmMsg::instantiate` and
693/// `WasmMsg::instantiate2`. tries to deserialize data using
694/// [`parse_instantiate_response_data`](https://docs.rs/cw-utils/latest/cw_utils/fn.parse_instantiate_response_data.html).
695///
696/// if `data` is:
697/// | `Some(valid)` | `Some(invalid)` | `None` |
698/// |---|---|---|
699/// | forwards `Some(valid)` | early returns error specifying what went wrong with `serde` error attached | Forwards `None` |
700///
701/// * Missing `#[sv::data(...)]` - In case `sv::data` is not found Sylvia won't forward the `data` argument
702/// so the `data` should be omited in the method signature.
703#[proc_macro_error]
704#[proc_macro_attribute]
705pub fn contract(attr: TokenStream, item: TokenStream) -> TokenStream {
706 contract_impl(attr.into(), item.into()).into()
707}
708
709fn contract_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
710 fn inner(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
711 let input: ItemImpl = parse2(item)?;
712 let expanded = if attr.is_empty() {
713 ContractInput::new(&input).process()
714 } else {
715 quote! {}
716 };
717 let input = StripInput.fold_item_impl(input);
718
719 Ok(quote! {
720 #[allow(clippy::new_without_default)]
721 #input
722
723 #expanded
724 })
725 }
726
727 inner(attr, item).unwrap_or_else(syn::Error::into_compile_error)
728}
729
730/// Procedural macro generating cosmwasm entry points from contract impl block.
731/// By default generates `execute`, `instantiate`, `sudo`, `query` entry points.
732///
733/// ## Example usage
734/// ```rust
735/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
736/// # use sylvia::ctx::ReplyCtx;
737/// # use sylvia::cw_std::{Binary, Reply, Response, StdResult, SubMsgResult};
738/// #
739/// pub struct SvContract;
740///
741/// ##[sylvia::entry_points]
742/// ##[sylvia::contract]
743/// ##[sv::features(replies)]
744/// impl SvContract {
745/// pub const fn new() -> Self {
746/// Self
747/// }
748///
749/// #[sv::msg(instantiate)]
750/// fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> StdResult<Response> {
751/// # Ok(Response::new())
752/// }
753/// #
754/// # #[sv::msg(exec)]
755/// # fn execute(&self, ctx: ExecCtx) -> StdResult<Response> {
756/// # Ok(Response::new())
757/// # }
758/// #
759/// # #[sv::msg(query)]
760/// # fn query(&self, ctx: QueryCtx) -> StdResult<Response> {
761/// # Ok(Response::new())
762/// # }
763/// #
764/// # #[sv::msg(migrate)]
765/// # fn migrate(&self, ctx: MigrateCtx) -> StdResult<Response> {
766/// # Ok(Response::new())
767/// # }
768/// #
769/// # #[sv::msg(reply)]
770/// # fn reply(&self, ctx: ReplyCtx, result: SubMsgResult, #[sv::payload(raw)] payload: Binary) -> StdResult<Response> {
771/// # Ok(Response::new())
772/// # }
773/// #
774/// # #[sv::msg(sudo)]
775/// # fn sudo(&self, ctx: SudoCtx) -> StdResult<Response> {
776/// # Ok(Response::new())
777/// # }
778/// }
779/// # fn main() {}
780/// ```
781///
782/// ## Generics
783///
784/// `Cosmwasm` entry point has to be implemented with concrete types.
785/// In case your contract uses some generic types you have to specify concrete types
786/// used in their place in the `entry_points` macro attribute `generics`.
787///
788/// ```rust
789/// # use sylvia::ctx::InstantiateCtx;
790/// # use sylvia::cw_std::{Response, StdResult};
791/// # use sylvia::cw_schema::cw_serde;
792/// # use cw_storage_plus::Item;
793/// # use std::marker::PhantomData;
794/// #
795/// # #[cw_serde]
796/// # pub struct SvCustomMsg;
797/// # impl sylvia::cw_std::CustomMsg for SvCustomMsg {}
798/// #
799/// pub struct SvContract<InstantiateT, DataT> {
800/// data: Item<DataT>,
801/// _phantom: PhantomData<InstantiateT>,
802/// }
803///
804/// ##[sylvia::entry_points(generics<SvCustomMsg, SvCustomMsg>)]
805/// ##[sylvia::contract]
806/// impl<InstantiateT, DataT> SvContract<InstantiateT, DataT>
807/// where InstantiateT: sylvia::types::CustomMsg + 'static,
808/// DataT: 'static
809/// {
810/// pub const fn new() -> Self {
811/// Self {
812/// data: Item::new("data"),
813/// _phantom: PhantomData,
814/// }
815/// }
816///
817/// #[sv::msg(instantiate)]
818/// fn instantiate(&self, ctx: InstantiateCtx, instantiate_data: InstantiateT) -> StdResult<Response> {
819/// # Ok(Response::new())
820/// }
821/// }
822/// # fn main() {}
823/// ```
824///
825#[proc_macro_error]
826#[proc_macro_attribute]
827pub fn entry_points(attr: TokenStream, item: TokenStream) -> TokenStream {
828 entry_points_impl(attr.into(), item.into()).into()
829}
830
831fn entry_points_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
832 fn inner(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
833 let input: ItemImpl = parse2(item)?;
834 let args = EntryPointArgs::new(&attr)?;
835 let expanded = EntryPointInput::new(&input, args, attr.span()).process();
836
837 Ok(quote! {
838 #input
839
840 #expanded
841 })
842 }
843
844 inner(attr, item).unwrap_or_else(syn::Error::into_compile_error)
845}
846
847#[cfg(test)]
848mod test {
849 use std::{env, fs};
850
851 use sylvia_runtime_macros::emulate_attribute_expansion_fallible;
852
853 use crate::{contract_impl, interface_impl};
854
855 // Test expanding macros in sylvia crate tests, to calculate generating code coverage
856 #[test]
857 fn sylvia_test_cov() {
858 let mut path = env::current_dir().unwrap();
859 path.push("..");
860 path.push("sylvia");
861 path.push("tests");
862
863 for entry in fs::read_dir(path).unwrap() {
864 let entry = entry.unwrap();
865
866 if entry.file_type().unwrap().is_file() {
867 let file = fs::File::open(entry.path()).unwrap();
868 emulate_attribute_expansion_fallible(file, "interface", interface_impl).unwrap();
869
870 let file = fs::File::open(entry.path()).unwrap();
871 emulate_attribute_expansion_fallible(file, "contract", contract_impl).unwrap();
872 }
873 }
874 }
875}