pchain_sdk_macros/lib.rs
1/*
2 Copyright © 2023, ParallelChain Lab
3 Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
4*/
5
6extern crate proc_macro;
7use proc_macro::TokenStream;
8use quote::quote;
9use syn::{ItemStruct, ItemImpl, NestedMeta, ItemTrait};
10
11
12mod core_impl;
13use self::core_impl::*;
14
15
16/// `contract` defines basic struct as a programming model of a contract.
17/// Fields are data representation of contract storage.
18///
19/// # Basic example
20/// Define fields in struct as contract storage. Define methods in impl as entrypoints
21///
22/// ```no_run
23/// #[contract]
24/// struct MyContract {
25/// data :i32
26/// }
27/// ```
28#[proc_macro_attribute]
29pub fn contract(_attr_args: TokenStream, input: TokenStream) -> TokenStream {
30
31 if let Ok(mut ist) = syn::parse::<ItemStruct>(input) {
32 generate_contract_struct(&mut ist)
33 } else {
34 generate_compilation_error("ERROR: contract macro can only be applied to smart contract Struct to read/write into world state".to_string())
35 }
36}
37
38/// `contract_methods` defines impl for the contract struct.
39/// Methods declared in the impl are callable by Transaction Command Call if their visibility is `pub`.
40///
41/// # Basic example
42/// Define contract methods which can be invoked by Transaction Command Call.
43///
44/// ```no_run
45/// #[contract_methods]
46/// impl MyContract {
47/// pub fn callable_function_a() {
48/// // ...
49/// }
50/// pub fn callable_function_b(input :i32) -> String {
51/// // ...
52/// }
53/// }
54/// ```
55#[proc_macro_attribute]
56pub fn contract_methods(_attr_args: TokenStream, input: TokenStream) -> TokenStream {
57 if let Ok(ipl) = syn::parse::<ItemImpl>(input) {
58 generate_contract_impl(&ipl)
59 } else {
60 generate_compilation_error("ERROR: contract_methods macro can only be applied to smart contract implStruct/implTrait.".to_string())
61 }
62}
63
64
65/// `use_contract` provides smart contract developers a way to make cross contract calls by using
66/// public functions from other smart contracts in the form of traits.
67///
68/// Examples are available at `smart_contract/examples`
69///
70/// # To use this macro
71/// ```no_run
72/// // The argument to `use_contract` is the address of the external smart contract to be called.
73/// // As rust enforces a unique name for each trait defined, it is important that the external
74/// // contract address is fed into `use_contract`. The trait name can therefore be anything.
75/// // However it is recommended to use a name similar to the external smart contract to be called.
76/// #[use_contract("Ns9DuNe8aS5QISfCyjEoAcZq20OVr2nKQTKsYGmo/Jw=")]
77/// pub trait MyContract {
78/// fn print_a_value();
79/// fn get_commodities_price(item: String) -> u64;
80/// }
81///
82///
83/// // .. MyContract struct definition .. //
84///
85/// #[contract_methods]
86/// impl MyContract {
87/// pub fn callable_function_a() {
88/// let gas: u64 = 100;
89/// let value: u64: 500;
90///
91/// // The functions from `MyContract` can now be called as associated functions. However you access
92/// //`MyContract` as `snake_case` instead of `CamelCase` as shown in the example.
93/// my_contract::print_a_value(100, 500);
94/// my_contract::get_commodities_price("sugar".to_string(),100,500);
95/// }
96/// }
97/// ```
98/// The available functions can be used anywhere at the crate level by the smart contract developer. As an example
99/// ```no_run
100/// // In external_call.rs
101/// use pchain_sdk::use_contract;
102///
103/// #[use_contract("Ns9DuNe8aS5QISfCyjEoAcZq20OVr2nKQTKsYGmo/Jw=")]
104/// pub trait MyContract {
105/// fn print_a_value();
106/// fn get_commodities_price(item: String) -> u64;
107/// }
108/// ```
109///
110/// ```no_run
111/// // In lib.rs
112///
113/// pub mod external_call;
114/// use external_call::my_contract;
115///
116/// #[contract_methods]
117/// impl MyContract {
118/// pub fn callable_function_a() {
119/// ...
120/// my_contract::print_a_value(100, 500);
121/// my_contract::get_commodities_price("sugar".to_string(),100,500);
122/// ...
123/// }
124/// }
125/// ```
126#[proc_macro_attribute]
127pub fn use_contract(attr_args: TokenStream, input: TokenStream) -> TokenStream {
128
129 let attr_args = syn::parse_macro_input!(attr_args as syn::AttributeArgs);
130 if attr_args.is_empty() || attr_args.len() > 2 {
131 return generate_compilation_error("At least one argument is required. Expect first argument to be a contract address. Second argument (Optional) to be 'action' or 'view'.".to_string());
132 };
133
134 match syn::parse::<ItemTrait>(input) {
135 Ok(it) => {
136 // `attr_args[0]` is the contract address of the external contract to be called.
137 let attr_contract_address = &attr_args[0];
138 let contract_address = match attr_contract_address {
139 NestedMeta::Lit(syn::Lit::Str(s)) => s.value(),
140 NestedMeta::Lit(_) | NestedMeta::Meta(_) => {
141 return generate_compilation_error("Only &str are allowed as first argument to use_contract".to_string())
142 },
143 };
144 generate_external_contract_mod(it, contract_address)
145 },
146 Err(_) => {
147 generate_compilation_error("use_contract can only be applied to trait definitions.".to_string())
148 },
149 }
150
151}
152
153/// The macro `contract_field` can generate impl so that nested struct can be supported in contract struct.
154///
155/// ### Example
156/// ```no_run
157/// #[contract_field]
158/// struct MyField {
159/// data: u64
160/// }
161///
162/// #[contract]
163/// struct MyContract {
164/// my_field :MyField
165/// }
166/// ```
167/// In the above example, the key used for storing in world-state will be "MyContract/my_field/data"
168/// while the value stored in world-state will be borse-serialized u64 data.
169///
170#[proc_macro_attribute]
171pub fn contract_field(_attr_args: TokenStream, input: TokenStream) -> TokenStream {
172 if let Ok(mut ist) = syn::parse::<ItemStruct>(input) {
173 let contract_field_struct = ist.clone();
174 let struct_impls:proc_macro2::TokenStream = generate_storage_impl(&mut ist).into();
175
176 TokenStream::from(
177 quote!{
178 #contract_field_struct
179
180 #struct_impls
181 }
182 )
183 } else {
184 generate_compilation_error("#[contract_field] can only be applied to struct definitions.".to_string())
185 }
186}
187
188/// `call` macro applies to impl methods for contract method call.
189///
190/// ### Example
191/// ```no_run
192/// #[call]
193/// fn action_method(d1: i32) -> String {
194/// // ...
195/// }
196/// ```
197#[proc_macro_attribute]
198pub fn call(_attr_args: TokenStream, input: TokenStream) -> TokenStream {
199 // it does nothing. The macro contract will handle this attribure.
200 input
201}