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