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}