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}