Skip to main content

dao_dao_macros/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
4
5use quote::quote;
6use syn::{parse_macro_input, AttributeArgs, DataEnum, DeriveInput, Path};
7
8// Merges the variants of two enums.
9fn merge_variants(metadata: TokenStream, left: TokenStream, right: TokenStream) -> TokenStream {
10    use syn::Data::Enum;
11
12    let args = parse_macro_input!(metadata as AttributeArgs);
13    if let Some(first_arg) = args.first() {
14        return syn::Error::new_spanned(first_arg, "macro takes no arguments")
15            .to_compile_error()
16            .into();
17    }
18
19    let mut left: DeriveInput = parse_macro_input!(left);
20    let right: DeriveInput = parse_macro_input!(right);
21
22    if let (
23        Enum(DataEnum { variants, .. }),
24        Enum(DataEnum {
25            variants: to_add, ..
26        }),
27    ) = (&mut left.data, right.data)
28    {
29        variants.extend(to_add);
30
31        quote! { #left }.into()
32    } else {
33        syn::Error::new(left.ident.span(), "variants may only be added for enums")
34            .to_compile_error()
35            .into()
36    }
37}
38
39/// Gets the dao_interface path for something exported by
40/// dao_interface. If we are currently compiling the dao-interface
41/// crate, `crate::{internal}` is returned. If we are not,
42/// `::dao_interface::{internal}` is returned.
43///
44/// The this is needed is that dao_interface both defines types used
45/// in the interfaces here, and uses the macros exported here. At some
46/// point we'll be in a compilation context where we're inside of a
47/// crate as the macro is being expanded, and we need to use types
48/// local to the crate.
49fn dao_interface_path(inside: &str) -> Path {
50    let pkg = std::env::var("CARGO_PKG_NAME").unwrap();
51    let base = if pkg == "dao-interface" {
52        "crate"
53    } else {
54        "::dao_interface"
55    };
56    let path = format!("{base}::{inside}");
57    let path: Path = syn::parse_str(&path).unwrap();
58    path
59}
60
61/// Adds the necessary fields to an enum such that the enum implements the
62/// interface needed to be a voting module.
63///
64/// For example:
65///
66/// ```
67/// use dao_dao_macros::voting_module_query;
68/// use cosmwasm_schema::{cw_serde, QueryResponses};
69/// use dao_interface::voting::TotalPowerAtHeightResponse;
70/// use dao_interface::voting::VotingPowerAtHeightResponse;
71///
72/// #[voting_module_query]
73/// #[cw_serde]
74/// #[derive(QueryResponses)]
75/// enum QueryMsg {}
76///
77/// ```
78/// Will transform the enum to:
79///
80/// ```
81///
82/// enum QueryMsg {
83///     VotingPowerAtHeight {
84///       address: String,
85///       height: Option<u64>
86///     },
87///     TotalPowerAtHeight {
88///       height: Option<u64>
89///     },
90///     Dao {},
91///     Info {},
92/// }
93/// ```
94///
95/// Note that other derive macro invocations must occur after this
96/// procedural macro as they may depend on the new fields. For
97/// example, the following will fail becase the `Clone` derivation
98/// occurs before the addition of the field.
99///
100/// ```compile_fail
101/// use dao_dao_macros::voting_module_query;
102/// use cosmwasm_schema::{cw_serde, QueryResponses};
103/// use dao_interface::voting::TotalPowerAtHeightResponse;
104/// use dao_interface::voting::VotingPowerAtHeightResponse;
105///
106/// #[derive(Clone)]
107/// #[voting_module_query]
108/// #[allow(dead_code)]
109/// #[cw_serde]
110/// #[derive(QueryResponses)]
111/// enum Test {
112///     #[returns(String)]
113///     Foo,
114///     #[returns(String)]
115///     Bar(u64),
116///     #[returns(String)]
117///     Baz { foo: u64 },
118/// }
119/// ```
120#[proc_macro_attribute]
121pub fn voting_module_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
122    let i = dao_interface_path("voting::InfoResponse");
123    let vp = dao_interface_path("voting::VotingPowerAtHeightResponse");
124    let tp = dao_interface_path("voting::TotalPowerAtHeightResponse");
125
126    merge_variants(
127        metadata,
128        input,
129        quote! {
130        enum Right {
131            /// Returns the voting power for an address at a given height.
132            #[returns(#vp)]
133            VotingPowerAtHeight {
134                address: ::std::string::String,
135                height: ::std::option::Option<::std::primitive::u64>
136            },
137            /// Returns the total voting power at a given block heigh.
138            #[returns(#tp)]
139            TotalPowerAtHeight {
140                height: ::std::option::Option<::std::primitive::u64>
141            },
142            /// Returns the address of the DAO this module belongs to.
143            #[returns(cosmwasm_std::Addr)]
144            Dao {},
145            /// Returns contract version info.
146            #[returns(#i)]
147            Info {}
148        }
149        }
150        .into(),
151    )
152}
153
154/// Adds the necessary fields to an enum such that it implements the
155/// interface needed to be a voting module with a cw20 token.
156///
157/// For example:
158///
159/// ```
160/// use dao_dao_macros::cw20_token_query;
161/// use cosmwasm_schema::{cw_serde, QueryResponses};
162/// use cosmwasm_std::Addr;
163///
164/// #[cw20_token_query]
165/// #[cw_serde]
166/// #[derive(QueryResponses)]
167/// enum QueryMsg {}
168/// ```
169///
170/// Will transform the enum to:
171///
172/// ```
173/// enum QueryMsg {
174///     TokenContract {},
175/// }
176/// ```
177///
178/// Note that other derive macro invocations must occur after this
179/// procedural macro as they may depend on the new fields. For
180/// example, the following will fail becase the `Clone` derivation
181/// occurs before the addition of the field.
182///
183/// ```compile_fail
184/// use dao_dao_macros::cw20_token_query;
185/// use cosmwasm_schema::{cw_serde, QueryResponses};
186///
187/// #[derive(Clone)]
188/// #[cw20_token_query]
189/// #[allow(dead_code)]
190/// #[cw_serde]
191/// #[derive(QueryResponses)]
192/// enum Test {
193///     Foo,
194///     Bar(u64),
195///     Baz { foo: u64 },
196/// }
197/// ```
198#[proc_macro_attribute]
199pub fn cw20_token_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
200    merge_variants(
201        metadata,
202        input,
203        quote! {
204        enum Right {
205            #[returns(::cosmwasm_std::Addr)]
206            TokenContract {}
207        }
208        }
209        .into(),
210    )
211}
212
213/// Adds the necessary fields to an enum such that it implements the
214/// interface needed to be a voting module with a native token.
215///
216/// For example:
217///
218/// ```
219/// use dao_dao_macros::native_token_query;
220/// use cosmwasm_schema::{cw_serde, QueryResponses};
221/// use cosmwasm_std::Addr;
222///
223/// #[native_token_query]
224/// #[cw_serde]
225/// #[derive(QueryResponses)]
226/// enum QueryMsg {}
227/// ```
228///
229/// Will transform the enum to:
230///
231/// ```
232/// enum QueryMsg {
233///     Denom {},
234/// }
235/// ```
236///
237/// Note that other derive macro invocations must occur after this
238/// procedural macro as they may depend on the new fields. For
239/// example, the following will fail becase the `Clone` derivation
240/// occurs before the addition of the field.
241///
242/// ```compile_fail
243/// use dao_dao_macros::native_token_query;
244/// use cosmwasm_schema::{cw_serde, QueryResponses};
245///
246/// #[derive(Clone)]
247/// #[native_token_query]
248/// #[allow(dead_code)]
249/// #[cw_serde]
250/// #[derive(QueryResponses)]
251/// enum Test {
252///     Foo,
253///     Bar(u64),
254///     Baz { foo: u64 },
255/// }
256/// ```
257#[proc_macro_attribute]
258pub fn native_token_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
259    merge_variants(
260        metadata,
261        input,
262        quote! {
263        enum Right {
264            #[returns(::dao_interface::voting::DenomResponse)]
265            Denom {}
266        }
267        }
268        .into(),
269    )
270}
271
272/// Adds the necessary fields to an enum such that it implements the
273/// interface needed to be a voting module that has an
274/// active check threshold.
275///
276/// For example:
277///
278/// ```
279/// use dao_dao_macros::active_query;
280/// use cosmwasm_schema::{cw_serde, QueryResponses};
281///
282/// #[active_query]
283/// #[cw_serde]
284/// #[derive(QueryResponses)]
285/// enum QueryMsg {}
286/// ```
287///
288/// Will transform the enum to:
289///
290/// ```
291/// enum QueryMsg {
292///     IsActive {},
293/// }
294/// ```
295///
296/// Note that other derive macro invocations must occur after this
297/// procedural macro as they may depend on the new fields. For
298/// example, the following will fail becase the `Clone` derivation
299/// occurs before the addition of the field.
300///
301/// ```compile_fail
302/// use dao_dao_macros::active_query;
303///
304/// #[derive(Clone)]
305/// #[active_query]
306/// #[allow(dead_code)]
307/// enum Test {
308///     Foo,
309///     Bar(u64),
310///     Baz { foo: u64 },
311/// }
312/// ```
313#[proc_macro_attribute]
314pub fn active_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
315    merge_variants(
316        metadata,
317        input,
318        quote! {
319        enum Right {
320            #[returns(::std::primitive::bool)]
321            IsActive {}
322        }
323        }
324        .into(),
325    )
326}
327
328/// Adds the necessary fields to an enum such that it implements the
329/// interface needed to be a proposal module.
330///
331/// For example:
332///
333/// ```
334/// use dao_dao_macros::proposal_module_query;
335/// use cosmwasm_schema::{cw_serde, QueryResponses};
336/// use cosmwasm_std::Addr;
337///
338/// #[proposal_module_query]
339/// #[cw_serde]
340/// #[derive(QueryResponses)]
341/// enum QueryMsg {}
342/// ```
343///
344/// Will transform the enum to:
345///
346/// ```
347/// enum QueryMsg {
348///     Dao {},
349///     Info {},
350///     ProposalCreationPolicy {},
351///     ProposalHooks {},
352/// }
353/// ```
354///
355/// Note that other derive macro invocations must occur after this
356/// procedural macro as they may depend on the new fields. For
357/// example, the following will fail becase the `Clone` derivation
358/// occurs before the addition of the field.
359///
360/// ```compile_fail
361/// use dao_dao_macros::proposal_module_query;
362/// use cosmwasm_schema::{cw_serde, QueryResponses};
363/// use cosmwasm_std::Addr;
364///
365/// #[derive(Clone)]
366/// #[proposal_module_query]
367/// #[allow(dead_code)]
368/// #[cw_serde]
369/// #[derive(QueryResponses)]
370/// enum Test {
371///     Foo,
372///     Bar(u64),
373///     Baz { foo: u64 },
374/// }
375/// ```
376#[proc_macro_attribute]
377pub fn proposal_module_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
378    let i = dao_interface_path("voting::InfoResponse");
379
380    merge_variants(
381        metadata,
382        input,
383        quote! {
384        enum Right {
385            /// Returns the address of the DAO this module belongs to
386            #[returns(::cosmwasm_std::Addr)]
387            Dao {},
388            /// Returns contract version info
389            #[returns(#i)]
390            Info { },
391            /// Returns the proposal ID that will be assigned to the
392            /// next proposal created.
393            #[returns(::std::primitive::u64)]
394            NextProposalId {},
395        }
396        }
397        .into(),
398    )
399}
400
401/// Limits the number of variants allowed on an enum at compile
402/// time. For example, the following will not compile:
403///
404/// ```compile_fail
405/// use dao_dao_macros::limit_variant_count;
406///
407/// #[limit_variant_count(1)]
408/// enum Two {
409///     One {},
410///     Two {},
411/// }
412/// ```
413#[proc_macro_attribute]
414pub fn limit_variant_count(metadata: TokenStream, input: TokenStream) -> TokenStream {
415    let args = parse_macro_input!(metadata as AttributeArgs);
416    if args.len() != 1 {
417        panic!("macro takes one argument. ex: `#[limit_variant_count(4)]`")
418    }
419
420    let limit: usize = if let syn::NestedMeta::Lit(syn::Lit::Int(unparsed)) = args.first().unwrap()
421    {
422        match unparsed.base10_parse() {
423            Ok(limit) => limit,
424            Err(e) => return e.to_compile_error().into(),
425        }
426    } else {
427        return syn::Error::new_spanned(args[0].clone(), "argument should be an integer literal")
428            .to_compile_error()
429            .into();
430    };
431
432    let ast: DeriveInput = parse_macro_input!(input);
433    match ast.data {
434        syn::Data::Enum(DataEnum { ref variants, .. }) => {
435            if variants.len() > limit {
436                return syn::Error::new_spanned(
437                    variants[limit].clone(),
438                    format!("this enum's variant count is limited to {limit}"),
439                )
440                .to_compile_error()
441                .into();
442            }
443        }
444        _ => {
445            return syn::Error::new(
446                ast.ident.span(),
447                "limit_variant_count may only be derived for enums",
448            )
449            .to_compile_error()
450            .into()
451        }
452    };
453
454    quote! {
455    #ast
456    }
457    .into()
458}