Skip to main content

psibase_macros_derive/
lib.rs

1//! This defines macros for the [fracpack crate](https://docs.rs/fracpack) and
2//! [psibase crate](https://docs.rs/psibase). See the documentation for those crates.
3
4use authorized_macro::authorized_attr_impl;
5use component_name_macro::component_name_macro_impl;
6use fracpack_macro::fracpack_macro_impl;
7use graphql_macro::{queries_macro_impl, table_query_macro_impl, table_query_subindex_macro_impl};
8use number_macro::{account_macro_impl, method_macro_impl};
9use plugin_error_macro::plugin_error_derive_impl;
10use proc_macro::TokenStream;
11use proc_macro_error::proc_macro_error;
12use psibase_macros_lib::service_macro::service_macro_impl;
13use psibase_macros_lib::service_tables_macro::service_tables_macro_impl;
14use schema_macro::schema_derive_macro;
15use test_case_macro::test_case_macro_impl;
16use to_key_macro::to_key_macro_impl;
17
18mod authorized_macro;
19mod component_name_macro;
20mod fracpack_macro;
21mod graphql_macro;
22mod number_macro;
23mod plugin_error_macro;
24mod schema_macro;
25mod test_case_macro;
26mod to_key_macro;
27
28#[proc_macro_error]
29#[proc_macro]
30pub fn component_name(_item: TokenStream) -> TokenStream {
31    component_name_macro_impl()
32}
33
34// TODO: remove
35#[proc_macro_derive(Fracpack, attributes(fracpack))]
36pub fn derive_fracpack(input: TokenStream) -> TokenStream {
37    fracpack_macro_impl(input, true, true)
38}
39
40#[proc_macro_derive(Pack, attributes(fracpack))]
41pub fn derive_pack(input: TokenStream) -> TokenStream {
42    fracpack_macro_impl(input, true, false)
43}
44
45#[proc_macro_derive(Unpack, attributes(fracpack))]
46pub fn derive_unpack(input: TokenStream) -> TokenStream {
47    fracpack_macro_impl(input, false, true)
48}
49
50#[proc_macro_derive(ToKey, attributes(to_key))]
51pub fn derive_to_key(input: TokenStream) -> TokenStream {
52    to_key_macro_impl(input)
53}
54
55/// # psibase_plugin::ErrorEnum
56///
57/// Allows psibase plugins to use a standard `thiserror::Error` enum, making it convertible
58/// to a standardized `host:types/types::Error` plugin error type. Standardizing on the error type
59/// allows chaining errors across plugin boundaries.
60///
61/// ## Usage
62///
63/// ### Step 1
64///
65/// Use the `host:types/types::Error` type as your `Error` variant for fallible plugin functions.
66/// ```wit
67/// # world.wit
68/// interface api {
69///     use host:types/types.{error};
70///     do-something: func() -> result<_, error>;
71/// }
72/// ```
73///
74/// ### Step 2
75///
76/// Tell wit-bindgen in your plugin to map `host:types/types` to `psibase_plugin::types`.
77/// If you're compiling your plugin using cargo-component, this is done via the following
78/// configuration in your `Cargo.toml`:
79///
80/// ```toml
81/// [package.metadata.component.bindings.with]
82/// "host:types/types" = "psibase_plugin::types"
83/// ```
84///
85/// ### Step 3
86///
87/// In your plugin rust code, add the `ErrorType` enum with the necessary macro derives:
88/// ```ignore
89/// #[derive(Debug, psibase_plugin::ErrorEnum, thiserror::Error)]
90/// #[repr(u32)]
91/// pub enum ErrorType {
92///     #[error("Parameter was invalid")]
93///     InvalidParameter = 1,
94///     #[error("Conversion error: {0}")]
95///     ConversionError(String),
96///     #[error("Custom error: {0}")]
97///     CustomError(String),
98/// ```
99///
100/// ### Step 4
101///
102/// Use the `ErrorType` enum in your plugin code:
103///
104/// ```ignore
105/// if something_is_wrong {
106///     return Err(ErrorType::CustomError("Something went wrong").into());
107/// }
108/// ```
109///
110#[proc_macro_error]
111#[proc_macro_derive(PluginError)]
112pub fn derive_plugin_error(input: TokenStream) -> TokenStream {
113    plugin_error_derive_impl(input)
114}
115
116/// # psibase_plugin::authorized
117///
118/// Allows psibase plugins to annotate their functions with trust levels and whitelists.
119/// For more information, see the [`psibase_plugin::trust`] documentation.
120#[proc_macro_error]
121#[proc_macro_attribute]
122pub fn authorized(attr: TokenStream, item: TokenStream) -> TokenStream {
123    authorized_attr_impl(attr, item)
124}
125
126#[proc_macro_derive(ToSchema, attributes(schema, fracpack))]
127pub fn to_schema(input: TokenStream) -> TokenStream {
128    schema_derive_macro(input)
129}
130
131/// Define a [psibase](https://psibase.io) service interface.
132///
133/// This macro defines the interface to a service so that other
134/// services, test cases, and apps which push transactions to the
135/// blockchain may use it. It also generates the documentation for
136/// the interface, using user-provided documentation as the source.
137///
138/// # Example
139///
140/// ```ignore
141/// /// This service adds and multiplies i32 numbers.
142/// ///
143/// /// This is where a detailed description would go.
144/// #[psibase::service]
145/// mod service {
146///     /// Add two numbers together.
147///     ///
148///     /// See also [Self::multiply].
149///     #[action]
150///     fn add(a: i32, b: i32) -> i32 {
151///         a + b
152///     }
153///
154///     /// Multiplies two numbers together.
155///     ///
156///     /// See also [Self::add].
157///     #[action]
158///     fn multiply(a: i32, b: i32) -> i32 {
159///         a * b
160///     }
161/// }
162/// ```
163///
164/// The service module and the actions within it may be private;
165/// the macro creates public definitions (below).
166///
167/// The macro copies the action documentation (like above) to the
168/// [`Actions<T>` methods](#actions-struct). Use the `[Self::...]`
169/// syntax like above within action documentation to refer to
170/// other actions.
171///
172/// # Recursion Safety
173///
174/// The [`recursive` option](#options), which defaults to false,
175/// controls whether the service can be reentered while it's
176/// currently executing. This prevents a series of exploits
177/// based on this pattern:
178///
179/// - Service `A` calls Service `B`
180/// - Service `B` calls back into Service `A`
181///
182/// Service `A` may opt into allowing recursion by setting the
183/// `recursive` option to true. This requires very careful
184/// design to prevent exploits. The following is a non-exhaustive
185/// list of potential attacks:
186///
187/// - `A` writes to a table, calls `B`, then writes to another
188///   table. Since it was in the middle of writing, `A`'s overall
189///   state is inconsistent. `B` calls a method on `A` which
190///   malfunctions because of the inconsistency between the
191///   two tables.
192/// - `A` reads some rows from a table then calls `B`. `B` calls an
193///   action in `A` which modifies the table. When `B` returns,
194///   `A` relies on the previously-read, but now out of date,
195///   data.
196/// - `A` calls `B` while iterating through a table index. `B` calls
197///   an action in `A` which modifies the table. When `B` returns,
198///   the iteration is now in an inconsistent state.
199///
200/// Rust's borrow checker doesn't prevent these attacks since
201/// nothing is mutably borrowed long term. Tables wrap psibase's
202/// [kv native functions](https://docs.rs/psibase/latest/psibase/native_raw/index.html),
203/// which treat the underlying KV store as if it were in an
204/// `UnsafeCell`. The Rust table wrappers can't protect against
205/// this since it's possible, and normal under recursion, to create
206/// multiple wrappers covering the same data range.
207///
208/// # Generated Output
209///
210/// The macro adds the following definitions to the service module:
211///
212/// ```ignore
213/// pub const  SERVICE: psibase::AccountNumber;
214/// pub struct Wrapper;
215/// pub struct Actions<T: psibase::Caller>;
216/// pub mod    action_structs;
217/// mod        service_wasm_interface;
218/// ```
219///
220/// It reexports `SERVICE`, `Wrapper`, `Actions`, and `action_structs` as public in
221/// the parent module. These names are [configurable](#options).
222///
223/// ## SERVICE constant
224///
225/// The `SERVICE` constant identifies the account the service is normally installed on. The
226/// macro generates this from the package name, but this can be overridden using the `name`
227/// option.
228///
229/// ## Wrapper struct
230///
231/// ```
232/// pub struct Wrapper;
233/// ```
234///
235/// The `Wrapper` struct makes it easy for other services, test cases, and Rust applications
236/// to call into the service. It has the following implementation:
237///
238/// ```ignore
239/// impl Wrapper {
240///     // The account this service normally runs on
241///     pub const SERVICE: psibase::AccountNumber;
242///
243///     // Call another service.
244///     //
245///     // `call_*` methods return an object which has methods (one per action) which
246///     // call another service and return the result from the call. These methods are
247///     // only usable by services.
248///     pub fn call() -> Actions<psibase::ServiceCaller>;
249///     pub fn call_to(service: psibase::AccountNumber)
250///     -> Actions<psibase::ServiceCaller>;
251///     pub fn call_from(sender: psibase::AccountNumber)
252///     -> Actions<psibase::ServiceCaller>;
253///     pub fn call_from_to(
254///         sender: psibase::AccountNumber,
255///         service: psibase::AccountNumber)
256///     -> Actions<psibase::ServiceCaller>;
257///
258///     // push transactions to psibase::Chain.
259///     //
260///     // `push_*` methods return an object which has methods (one per action) which
261///     // push transactions to a test chain and return a psibase::ChainResult or
262///     // psibase::ChainEmptyResult. This final object can verify success or failure
263///     // and can retrieve the return value, if any.
264///     pub fn push(
265///         chain: &psibase::Chain,
266///     ) -> Actions<psibase::ChainPusher>;
267///     pub fn push_to(
268///         chain: &psibase::Chain,
269///         service: psibase::AccountNumber,
270///     ) -> Actions<psibase::ChainPusher>;
271///     pub fn push_from(
272///         chain: &psibase::Chain,
273///         sender: psibase::AccountNumber,
274///     ) -> Actions<psibase::ChainPusher>;
275///     pub fn push_from_to(
276///         chain: &psibase::Chain,
277///         sender: psibase::AccountNumber,
278///         service: psibase::AccountNumber,
279///     ) -> Actions<psibase::ChainPusher>;
280///
281///     // Pack actions into psibase::Action.
282///     //
283///     // `pack_*` functions return an object which has methods (one per action)
284///     // which pack the action's arguments using fracpack and return a psibase::Action.
285///     // The `pack_*` series of functions is mainly useful to applications which
286///     // push transactions to blockchains.
287///     pub fn pack() -> Actions<psibase::ActionPacker>;
288///     pub fn pack_to(
289///         service: psibase::AccountNumber,
290///     ) -> Actions<psibase::ActionPacker>;
291///     pub fn pack_from(
292///         sender: psibase::AccountNumber,
293///     ) -> Actions<psibase::ActionPacker>;
294///     pub fn pack_from_to(
295///         sender: psibase::AccountNumber,
296///         service: psibase::AccountNumber,
297///     ) -> Actions<psibase::ActionPacker>;
298/// }
299/// ```
300///
301/// ## Actions struct
302///
303/// ```ignore
304/// pub struct Actions<T: psibase::Caller> {
305///     pub caller: T,
306/// }
307/// ```
308///
309/// This struct's implementation contains a public method for each action. The methods have
310/// the same names and arguments as the actions. The methods pass their arguments as a tuple
311/// to either `Caller::call` or `Caller::call_returns_nothing`, returning the final result.
312///
313/// `Actions<T>` is part of the glue which makes `Wrapper` work; `Wrapper` methods return
314/// `Actions<T>` instances with the appropriate inner `caller`. `Actions<T>` also documents
315/// the actions themselves.
316///
317/// ## action_structs module
318///
319/// ```ignore
320/// pub mod action_structs {...}
321/// ```
322///
323/// `action_structs` contains a public struct for each action. Each struct has the same
324/// name as its action and has the same fields as the action's arguments. The structs
325/// implement `fracpack::Packable`.
326///
327/// ## service_wasm_interface module
328///
329/// This module defines the `start` and `called` WASM entry points. psinode
330/// calls `start` to initialize the WASM whenever it is used within a
331/// transaction. psinode calls `called` every time another service calls into
332/// this WASM. `called` deserializes action data, calls into the appropriate
333/// action function, and serializes the return value.
334///
335/// # Dead code warnings
336///
337/// When the [dispatch option](#options) is false, there is usually no code
338/// remaining which calls the actions. The service macro adds `#[allow(dead_code)]`
339/// to the service module when the dispatch option is false to prevent the
340/// compiler from warning about it.
341///
342/// # Options
343///
344/// The service attribute has the following options. The defaults are shown:
345///
346/// ```ignore
347/// #[psibase::service(
348///     name = see_below,            // Account service is normally installed on
349///     recursive = false,          // Allow service to be recursively entered?
350///     constant = "SERVICE",       // Name of generated constant
351///     actions = "Actions",        // Name of generated struct
352///     wrapper = "Wrapper",        // Name of generated struct
353///     structs = "action_structs", // Name of generated module
354///     dispatch = see_below,       // Create service_wasm_interface?
355///     pub_constant = true,        // Make constant public and reexport it?
356/// )]
357/// ```
358///
359/// `name` defaults to the package name.
360///
361/// `dispatch` defaults to true if the `CARGO_PRIMARY_PACKAGE` environment
362/// variable is set, and false otherwise. Cargo sets this variable automatically.
363/// For example, assume you have two services, A and B. B brings in A as a
364/// dependency so it can use A's wrappers to call it. When cargo builds A,
365/// `dispatch` will default to true in A's service definition. When cargo builds
366/// B, `dispatch` will default to true in B's service definition but false
367/// in A's. This prevents B from accidentally including A's dispatch.
368///
369/// If the `CARGO_PSIBASE_TEST` environment variable is set, then the macro
370/// forces `dispatch` to false. `cargo psibase test` sets `CARGO_PSIBASE_TEST`
371/// to prevent tests from having service entry points.
372#[proc_macro_error]
373#[proc_macro_attribute]
374pub fn service(attr: TokenStream, item: TokenStream) -> TokenStream {
375    service_macro_impl(
376        proc_macro2::TokenStream::from(attr),
377        proc_macro2::TokenStream::from(item),
378    )
379    .into()
380}
381
382#[proc_macro_error]
383#[proc_macro_attribute]
384pub fn service_tables(attr: TokenStream, item: TokenStream) -> TokenStream {
385    service_tables_macro_impl(
386        proc_macro2::TokenStream::from(attr),
387        proc_macro2::TokenStream::from(item),
388    )
389    .into()
390}
391
392/// Define a [psibase](https://psibase.io) test case.
393///
394/// Psibase tests run in WASM. They create block chains, push transactions,
395/// and check the success or failure of those transactions to verify
396/// correct service operation.
397///
398/// # Example
399///
400/// ```ignore
401/// #[psibase::service]
402/// mod service {
403///     #[action]
404///     fn add(a: i32, b: i32) -> i32 {
405///         println!("Let's add {} and {}", a, b);
406///         println!("Hopefully the result is {}", a + b);
407///         a + b
408///     }
409/// }
410///
411/// #[psibase::test_case(services("example"))]
412/// fn test_arith(chain: psibase::Chain) -> Result<(), psibase::Error> {
413///     // Verify the action works as expected.
414///     assert_eq!(Wrapper::push(&chain).add(3, 4).get()?, 7);
415///
416///     // Start a new block; this prevents the following transaction
417///     // from being rejected as a duplicate.
418///     chain.start_block();
419///
420///     // Print a trace; this allows us to see:
421///     // * The service call chain. Something calls our service;
422///     //   let's see what it is!
423///     // * The service's prints, which are normally invisible
424///     //   during testing
425///     println!(
426///         "\n\nHere is the trace:\n{}",
427///         Wrapper::push(&chain).add(3, 4).trace
428///     );
429///
430///     // If we got this far, then the test has passed
431///     Ok(())
432/// }
433/// ```
434///
435/// You may define unit tests within service sources, or define separate
436/// integration tests.
437///
438/// # Running tests
439///
440/// ```text
441/// cargo psibase test
442/// ```
443///
444/// This builds and runs both unit and integration tests. It also builds
445/// any services the tests depend on. These appear in the `test_case`
446/// macro's `services` parameter, or as arguments to the `include_service`
447/// macro.
448///
449/// # Loading services
450///
451/// Services in the `services` parameter or in the `include_service` macro
452/// reference packages which define services. They may be any of the
453/// following:
454///
455/// * The name of the current package
456/// * The name of any package the current package depends on
457/// * The name of any package in the current workspace, if any
458///
459/// If the test function has an argument, e.g. `my_test(chain: psibase::Chain)`,
460/// then the macro initializes a fresh chain, loads it with the requested
461/// services, and passes it to the function. If the test function doesn't
462/// have an argument, then the test may initialize a chain and load services
463/// itself, like the following example.
464///
465/// ```ignore
466/// #[psibase::test_case]
467/// fn my_test() -> Result<(), psibase::Error> {
468///     // TODO
469/// }
470/// ```
471///
472/// The `include_service` macro is only available to functions which have
473/// the `psibase::test_case` attribute. It's not defined outside of these
474/// functions.
475#[proc_macro_error]
476#[proc_macro_attribute]
477pub fn test_case(attr: TokenStream, item: TokenStream) -> TokenStream {
478    test_case_macro_impl(attr, item)
479}
480
481#[proc_macro_error]
482#[proc_macro]
483pub fn account(item: TokenStream) -> TokenStream {
484    account_macro_impl(true, item)
485}
486
487#[proc_macro_error]
488#[proc_macro]
489pub fn account_raw(item: TokenStream) -> TokenStream {
490    account_macro_impl(false, item)
491}
492
493#[proc_macro_error]
494#[proc_macro]
495pub fn method(item: TokenStream) -> TokenStream {
496    method_macro_impl(true, item)
497}
498
499#[proc_macro_error]
500#[proc_macro]
501pub fn method_raw(item: TokenStream) -> TokenStream {
502    method_macro_impl(false, item)
503}
504
505#[proc_macro_error]
506#[proc_macro_attribute]
507pub fn queries(attr: TokenStream, item: TokenStream) -> TokenStream {
508    queries_macro_impl(attr, item)
509}
510
511#[proc_macro_error]
512#[proc_macro]
513pub fn table_query(item: TokenStream) -> TokenStream {
514    table_query_macro_impl(item)
515}
516
517#[proc_macro_error]
518#[proc_macro]
519pub fn table_query_subindex(item: TokenStream) -> TokenStream {
520    table_query_subindex_macro_impl(item)
521}