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}