obce_macro/
lib.rs

1// Copyright (c) 2012-2022 Supercolony
2//
3// Permission is hereby granted, free of charge, to any person obtaining
4// a copy of this software and associated documentation files (the"Software"),
5// to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to
8// permit persons to whom the Software is furnished to do so, subject to
9// the following conditions:
10//
11// The above copyright notice and this permission notice shall be
12// included in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22#![cfg_attr(not(feature = "std"), no_std)]
23
24use proc_macro::TokenStream;
25
26use obce_codegen::{
27    definition,
28    error,
29    extension,
30    id,
31    implementation,
32    mock,
33};
34
35/// Chain extension definition for use with Substrate-based nodes and ink! smart contracts.
36///
37/// # Description
38///
39/// This macro generates code based on activated OBCE features.
40///
41/// When used with `ink` feature, [`#[obce::definition]`](macro@definition) generates
42/// a glue code to correctly call your chain extension from ink! smart contracts.
43///
44/// The behaviour of [`#[obce::definition]`](macro@definition) with `substrate` feature enabled
45/// is to leave everything as-is, without any additional modifications.
46///
47/// ```ignore
48/// pub struct ChainExtension;
49///
50/// #[obce::definition]
51/// pub trait ChainExtensionDefinition {
52///     fn some_method(&self, argument: u32) -> u64;
53/// }
54/// ```
55///
56/// # Custom identifiers
57///
58/// You can use `#[obce::definition(id = ...)]` and `#[obce(id = ...)]` to override
59/// the automatically generated chain extension identifier and chain extension method identifier
60/// correspondingly.
61///
62/// `id` accepts literals of type [`&str`] and [`u16`].
63#[proc_macro_attribute]
64pub fn definition(attrs: TokenStream, trait_item: TokenStream) -> TokenStream {
65    match definition::generate(attrs.into(), trait_item.into()) {
66        Ok(traits) => traits.into(),
67        Err(err) => err.to_compile_error().into(),
68    }
69}
70
71/// Chain extension implementation for use with Substrate-based nodes.
72///
73/// # Description
74///
75/// This macro generates the necessary trait implementations for you to use
76/// your chain extension with Substrate runtime.
77///
78/// This macro checks for the generics that you use in your impl block.
79///
80/// ```ignore
81/// use obce::substrate::{
82///     frame_system::Config as SysConfig,
83///     pallet_contracts::Config as ContractConfig,
84///     sp_runtime::traits::StaticLookup,
85///     ChainExtensionEnvironment,
86///     ExtensionContext
87/// };
88///
89/// pub struct ChainExtension;
90///
91/// #[obce::definition]
92/// pub trait ChainExtensionDefinition {
93///     fn extension_method(&self);
94/// }
95///
96/// #[obce::implementation]
97/// impl<'a, E, T, Env> ChainExtensionDefinition for ExtensionContext<'a, E, T, Env, ChainExtension>
98/// where
99///     T: SysConfig + ContractConfig,
100///     <<T as SysConfig>::Lookup as StaticLookup>::Source: From<<T as SysConfig>::AccountId>,
101///     Env: ChainExtensionEnvironment<E, T>,
102/// {
103///     fn extension_method(&self) {
104///         // Do awesome stuff!
105///     }
106/// }
107/// ```
108///
109/// # Generics
110///
111/// `E` represents the external environment in which smart contracts are being executed.
112/// When building chain extension without OBCE, it is usually bounded by `pallet_contracts::chain_extension::Ext`,
113/// providing you access to methods that interacts with the execution environment. However,
114/// to provide you with better testing capabilities OBCE does not bound the `E` generic itself,
115/// resorting to bound the `Env` with it instead.
116///
117/// `T` represents your configuration type, which can be bounded by pallet-specific configuration traits
118/// (such as `pallet_contracts::pallet::Config` and `frame_system::Config`).
119///
120/// `Env` generic is used to represent the OBCE-specific chain extension environment, which is more easily
121/// testable, and can additionally be bounded by any trait you want to use. For example, you can add a trait that
122/// represents your chain-specific pallet and use it inside of your chain extension.
123///
124/// # Weight charging
125///
126/// You can use `#[obce(weight(dispatch = ...))]` to automatically charge
127/// weight based on a pallet call dispatch information.
128///
129/// `dispatch` accepts a full path to pallet's call (for example, `pallet_example::Pallet::<T>::my_call`).
130///
131/// OBCE will attempt to automatically obtain dispatch info based on the arguments passed
132/// to your chain extension method.
133///
134/// If pallet's call arguments and your chain extension method
135/// arguments are different, you can use `args` to override them:
136/// `#[obce(weight(dispatch = "pallet_example::Pallet::<T>::my_call", args = "some_val,123"))]`.
137///
138/// You can also use `#[obce(weight(expr = ...))]` to charge weight without pallet calls.
139/// In this case, you can simply provide any expression which returns `Weight`:
140/// `#[obce(weight(expr = "Weight::from_parts(ref_time, proof_size)"))]`.
141///
142/// OBCE also provides you with a pre-charging feature, which charges weight before
143/// any data parsing is done, making sure that weight is paid even if the call
144/// is not successful:
145///
146/// ```ignore
147/// use obce::substrate::{
148///     sp_weights::Weight,
149///     frame_system::Config as SysConfig,
150///     pallet_contracts::Config as ContractConfig,
151///     sp_runtime::traits::StaticLookup,
152///     ChainExtensionEnvironment,
153///     ExtensionContext
154/// };
155///
156/// pub struct ChainExtension;
157///
158/// #[obce::definition]
159/// pub trait ChainExtensionDefinition {
160///     fn extension_method(&mut self, val: u64);
161/// }
162///
163/// #[obce::implementation]
164/// impl<'a, E, T, Env> ChainExtensionDefinition for ExtensionContext<'a, E, T, Env, ChainExtension>
165/// where
166///     T: SysConfig + ContractConfig,
167///     <<T as SysConfig>::Lookup as StaticLookup>::Source: From<<T as SysConfig>::AccountId>,
168///     Env: ChainExtensionEnvironment<E, T>,
169/// {
170///     #[obce(weight(expr = "Weight::from_parts(123, 0)", pre_charge))]
171///     fn extension_method(&mut self, _val: u64) {
172///         self.pre_charged().unwrap();
173///     }
174/// }
175///
176/// fn main() {}
177/// ```
178///
179/// ## Usage example
180///
181/// ```ignore
182/// use obce::substrate::{
183///     frame_system::{Config as SysConfig, RawOrigin},
184///     pallet_contracts::Config as ContractConfig,
185///     sp_runtime::traits::StaticLookup,
186///     ChainExtensionEnvironment,
187///     ExtensionContext
188/// };
189///
190/// pub struct ChainExtension;
191///
192/// #[obce::definition]
193/// pub trait ChainExtensionDefinition {
194///     fn extension_method(&mut self, val: u64);
195/// }
196///
197/// #[obce::implementation]
198/// impl<'a, E, T, Env> ChainExtensionDefinition for ExtensionContext<'a, E, T, Env, ChainExtension>
199/// where
200///     T: SysConfig + ContractConfig + pallet_example::Config,
201///     <<T as SysConfig>::Lookup as StaticLookup>::Source: From<<T as SysConfig>::AccountId>,
202///     Env: ChainExtensionEnvironment<E, T>,
203/// {
204///     #[obce(weight(dispatch = "pallet_example::Pallet::<T>::test_method", args = "123"))]
205///     fn extension_method(&mut self, val: u64) {
206///         // ...
207///     }
208/// }
209/// ```
210///
211/// ## `Ext` trait bounds
212///
213/// You may notice that the example above doesn't have `E: Ext<T = T>` bound, which is required
214/// when calling your chain extension via `pallet_contracts::chain_extension::ChainExtension`.
215///
216/// This is because OBCE automatically generates two separate trait implementations for your
217/// chain extension struct - `obce::substrate::CallableChainExtension` and `pallet_contracts::chain_extension::ChainExtension`.
218///
219/// Only when generating the latter OBCE automatically adds `E: Ext<T = T>` bound, while still providing
220/// you capabilities to manually add `E: Ext<T = T>` on the implementation trait bounds to allow `Ext` trait
221/// usage inside implementation methods:
222///
223/// ```ignore
224/// use obce::substrate::{
225///     frame_system::{Config as SysConfig, RawOrigin},
226///     pallet_contracts::{
227///         chain_extension::Ext,
228///         Config as ContractConfig,
229///     },
230///     sp_runtime::traits::StaticLookup,
231///     ChainExtensionEnvironment,
232///     ExtensionContext
233/// };
234///
235/// pub struct ChainExtension;
236///
237/// #[obce::definition]
238/// pub trait ChainExtensionDefinition {
239///     fn extension_method(&mut self, val: u64);
240/// }
241///
242/// #[obce::implementation]
243/// impl<'a, E, T, Env> ChainExtensionDefinition for ExtensionContext<'a, E, T, Env, ChainExtension>
244/// where
245///     T: SysConfig + ContractConfig + pallet_example::Config,
246///     <<T as SysConfig>::Lookup as StaticLookup>::Source: From<<T as SysConfig>::AccountId>,
247///     Env: ChainExtensionEnvironment<E, T>,
248///     E: Ext<T = T>,
249/// {
250///     fn extension_method(&mut self, val: u64) {
251///         // Ext trait can be used here
252///     }
253/// }
254/// ```
255///
256/// This is done to ease chain extension environment generalization during testing.
257#[proc_macro_attribute]
258pub fn implementation(attrs: TokenStream, impl_item: TokenStream) -> TokenStream {
259    match implementation::generate(attrs.into(), impl_item.into()) {
260        Ok(impls) => impls.into(),
261        Err(err) => err.to_compile_error().into(),
262    }
263}
264
265/// Chain extension error.
266///
267/// # Description
268///
269/// Using [`#[obce::error]`](macro@error) you can generate custom chain extension
270/// errors.
271///
272/// Errors marked with [`#[obce::error]`](macro@error) have [`Debug`], [`Copy`], [`Clone`], [`PartialEq`], [`Eq`], `scale::Encode` and `scale::Decode`
273/// automatically derived for them.
274///
275/// ```ignore
276/// #[obce::error]
277/// enum Error {
278///     FirstError,
279///     SecondError(u32)
280/// }
281/// ```
282///
283/// # Critical errors
284///
285/// [`#[obce::error]`](macro@error) can automatically generate `SupportCriticalError`
286/// implementation for variant that you mark with `#[obce(critical)]`:
287///
288/// ```ignore
289/// use obce::substrate::CriticalError;
290///
291/// #[obce::error]
292/// enum Error {
293///     FirstError,
294///
295///     #[obce(critical)]
296///     Two(CriticalError)
297/// }
298/// ```
299///
300/// Only one enum variant can be marked as `#[obce(critical)]`.
301///
302/// # `RetVal`-convertible errors
303///
304/// You can mark error variants with `#[obce(ret_val = "...")]` to create an implementation of
305/// [`TryFrom<YourError>`](::core::convert::TryFrom) for `pallet_contracts::chain_extension::RetVal`,
306/// which will automatically convert suitable error variants to `RetVal` on implementation methods marked with `#[obce(ret_val)]`.
307///
308/// Error variant's `#[obce(ret_val = "...")]` accepts an expression that evaluates to [`u32`]:
309///
310/// ```ignore
311/// #[obce::error]
312/// enum Error {
313///     #[obce(ret_val = "10_001")]
314///     First,
315///
316///     Second
317/// }
318/// ```
319#[proc_macro_attribute]
320pub fn error(attrs: TokenStream, enum_item: TokenStream) -> TokenStream {
321    match error::generate(attrs.into(), enum_item.into()) {
322        Ok(tokens) => tokens.into(),
323        Err(err) => err.to_compile_error().into(),
324    }
325}
326
327/// Chain extension mocking utility.
328///
329/// # Description
330///
331/// You can use [`#[obce::mock]`](macro@mock) to automatically generate `register_chain_extensions`
332/// function, which accepts a context and automatically registers mocked chain extension methods
333/// for off-chain ink! smart contract testing.
334///
335/// Such a testing is useful to check smart contract's behaviour in the absence of
336/// an available node.
337///
338/// ```ignore
339/// // ink! smart contract definition is omitted.
340///
341/// #[obce::definition]
342/// pub trait MyChainExtension {
343///     fn test_method(&mut self, val: u32, another_val: u32) -> u32;
344/// }
345///
346/// #[obce::mock]
347/// impl MyChainExtension for () {
348///     fn test_method(&mut self, val: u32, another_val: u32) -> u32 {
349///         val + another_val
350///     }
351/// }
352///
353/// #[test]
354/// fn call_contract() {
355///     register_chain_extensions(());
356///     let mut contract = SimpleContract::new();
357///     assert_eq!(contract.call_test_method(100, 200), 300);
358/// }
359/// ```
360///
361/// When using [`#[obce::mock]`](macro@mock), you are not required to fill every single
362/// method for testing. Glue code to register chain extension methods will only apply to
363/// those methods, that you listed in a mock macro call:
364///
365/// ```ignore
366/// #[obce::definition]
367/// pub trait MyChainExtension {
368///     fn first_method(&mut self, val: u32) -> u32;
369///     fn second_method(&mut self) -> u64;
370/// }
371///
372/// #[obce::mock]
373/// impl MyChainExtension for () {
374///     fn first_method(&mut self, val: u32) -> u32 {
375///         // ...
376///     }
377///
378///     // second_method is not required to be present here
379/// }
380/// ```
381///
382/// If an attempt is made to make a call to a missing method a panic with `UnregisteredChainExtension`
383/// message will be issued.
384///
385/// # Context
386///
387/// The item that you implement your definition trait for becomes your testing context.
388///
389/// You will receive the same testing context when calling methods multiple times,
390/// thus it can be used as your chain extension testing state:
391///
392/// ```ignore
393/// #[obce::definition]
394/// pub trait Trait {
395///     fn method(&mut self) -> u32;
396/// }
397///
398/// #[obce::ink_lang::extension]
399/// struct TestExtension;
400///
401/// impl Trait for TestExtension {}
402///
403/// #[ink::contract]
404/// mod simple_contract {
405///     use crate::{
406///         TestExtension,
407///         Trait,
408///     };
409///
410///     #[ink(storage)]
411///     pub struct SimpleContract {}
412///
413///     impl SimpleContract {
414///         #[ink(constructor)]
415///         pub fn new() -> Self {
416///             SimpleContract {}
417///         }
418///
419///         #[ink(message)]
420///         pub fn call_method(&mut self) -> u32 {
421///             TestExtension.method()
422///         }
423///     }
424/// }
425///
426/// mod state_test {
427///     #[derive(Clone, Default)]
428///     pub struct State {
429///         call_count: u32,
430///     }
431///
432///     #[obce::mock]
433///     impl crate::Trait for State {
434///         fn method(&mut self) -> u32 {
435///             self.call_count += 1;
436///             self.call_count
437///         }
438///     }
439///
440///     #[test]
441///     fn call_contract() {
442///         register_chain_extensions(State::default());
443///         let mut contract = crate::simple_contract::SimpleContract::new();
444///         assert_eq!(contract.call_method(), 1);
445///         assert_eq!(contract.call_method(), 2);
446///         assert_eq!(contract.call_method(), 3);
447///     }
448/// }
449/// ```
450///
451/// # General guidelines
452///
453/// Since [`#[obce::mock]`](macro@mock) is designed for off-chain testing, you are
454/// limited by off-chain testing facilities that [ink! library provides](https://use.ink/basics/contract-testing).
455///
456/// # Complete example
457///
458/// ```ignore
459/// #[obce::definition(id = 123)]
460/// pub trait ChainExtension {
461///     fn method(&mut self, val: u32, another_val: u32) -> u32;
462///
463///     #[obce(id = 456)]
464///     fn another_method(&mut self, val: u32) -> u32;
465/// }
466///
467/// #[obce::ink_lang::extension]
468/// struct MyChainExtension;
469///
470/// impl ChainExtension for MyChainExtension {}
471///
472/// #[ink::contract]
473/// mod simple_contract {
474///     use crate::{
475///         ChainExtension,
476///         MyChainExtension,
477///     };
478///
479///     #[ink(storage)]
480///     pub struct SimpleContract {}
481///
482///     impl SimpleContract {
483///         #[ink(constructor)]
484///         pub fn new() -> Self {
485///             SimpleContract {}
486///         }
487///
488///         #[ink(message)]
489///         pub fn call_method(&mut self, val: u32, another_val: u32) -> u32 {
490///             MyChainExtension.method(val, another_val)
491///         }
492///
493///         #[ink(message)]
494///         pub fn call_another_method(&mut self, val: u32) -> u32 {
495///             MyChainExtension.another_method(val)
496///         }
497///     }
498/// }
499///
500/// mod simple_test {
501///     #[obce::mock]
502///     impl crate::ChainExtension for () {
503///         fn method(&mut self, val: u32, another_val: u32) -> u32 {
504///             val + another_val
505///         }
506///     }
507///
508///     #[test]
509///     fn call_contract() {
510///         register_chain_extensions(());
511///         let mut contract = crate::simple_contract::SimpleContract::new();
512///         assert_eq!(contract.call_method(100, 200), 300);
513///     }
514/// }
515/// ```
516#[proc_macro_attribute]
517pub fn mock(attrs: TokenStream, enum_item: TokenStream) -> TokenStream {
518    match mock::generate(attrs.into(), enum_item.into()) {
519        Ok(tokens) => tokens.into(),
520        Err(err) => err.to_compile_error().into(),
521    }
522}
523
524/// ink! chain extension marker.
525///
526/// # Description
527///
528/// Using this macro, you can mark your ink! chain extension structs to
529/// be instantiable using ink!'s environment.
530///
531/// # Example
532///
533/// ```ignore
534/// #[obce::definition]
535/// pub trait Trait {
536///     fn method(&mut self, val: u32, another_val: u32) -> u32;
537/// }
538///
539/// #[obce::ink_lang::extension]
540/// struct TestExtension;
541///
542/// impl Trait for TestExtension {}
543/// ```
544///
545/// # Usage with ink!
546///
547/// To integrate such an extension with ink!, you can use the following example:
548///
549/// ```ignore
550/// use ink::env::{DefaultEnvironment, Environment};
551///
552/// #[derive(Debug, Clone, PartialEq, Eq)]
553/// #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
554/// pub enum CustomEnvironment {}
555///
556/// impl Environment for CustomEnvironment {
557///     const MAX_EVENT_TOPICS: usize =
558///         <DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;
559///
560///     type AccountId = <DefaultEnvironment as Environment>::AccountId;
561///     type Balance = <DefaultEnvironment as Environment>::Balance;
562///     type Hash = <DefaultEnvironment as Environment>::Hash;
563///     type BlockNumber = <DefaultEnvironment as Environment>::BlockNumber;
564///     type Timestamp = <DefaultEnvironment as Environment>::Timestamp;
565///
566///     type ChainExtension = TestExtension;
567/// }
568/// ```
569#[proc_macro_attribute]
570pub fn ink_extension(attrs: TokenStream, struct_item: TokenStream) -> TokenStream {
571    match extension::ink(attrs.into(), struct_item.into()) {
572        Ok(tokens) => tokens.into(),
573        Err(err) => err.to_compile_error().into(),
574    }
575}
576
577/// Chain extension identifier lookup.
578///
579/// # Description
580///
581/// Using [`obce::id!`](macro@id) macro, you can lookup chain extension and chain extension method identifiers.
582///
583/// # Example
584///
585/// ```ignore
586/// #[obce::definition(id = 123)]
587/// pub trait ChainExtension {
588///     #[obce(id = 456)]
589///     fn method(&self);
590/// }
591///
592/// assert_eq!(obce::id!(ChainExtension), 123);
593/// assert_eq!(obce::id!(ChainExtension::method), 456);
594/// ```
595///
596/// # Supported paths
597///
598/// To correctly distinguish between a chain extension itself and a chain extension method,
599/// you have to provide a path with at most two segments (for example, `ChainExtension`, `SomeExtension::method`).
600///
601/// The macro will provide you with an error message in case if the path you provided is incorrect.
602#[proc_macro]
603pub fn id(path: TokenStream) -> TokenStream {
604    match id::generate(path.into()) {
605        Ok(tokens) => tokens.into(),
606        Err(error) => error.to_compile_error().into(),
607    }
608}