near_sdk_contract_tools/hook.rs
1//! # Hooks
2//!
3//! Hooks are a way to wrap (inject code before and after) component functions.
4//!
5//! Most of the time, hooks are used to implement cross-cutting concerns, such as
6//! logging, accounting, or integration with other components.
7//!
8//! ## Example
9//!
10//! ```
11//! use near_sdk::{env, log, near, PanicOnDefault};
12//! use near_sdk_contract_tools::{hook::Hook, standard::nep141::*, Nep141};
13//!
14//! pub struct MyTransferHook;
15//!
16//! impl Hook<MyContract, Nep141Transfer<'_>> for MyTransferHook {
17//! fn hook<R>(contract: &mut MyContract, transfer: &Nep141Transfer<'_>, f: impl FnOnce(&mut MyContract) -> R) -> R {
18//! // Log, check preconditions, save state, etc.
19//! log!("NEP-141 transfer from {} to {} of {} tokens", transfer.sender_id, transfer.receiver_id, transfer.amount);
20//!
21//! let storage_usage_before = env::storage_usage();
22//!
23//! let r = f(contract); // execute wrapped function
24//!
25//! let storage_usage_after = env::storage_usage();
26//! log!("Storage delta: {}", storage_usage_after - storage_usage_before);
27//!
28//! r
29//! }
30//! }
31//!
32//! #[derive(Nep141, PanicOnDefault)]
33//! #[nep141(transfer_hook = "MyTransferHook")]
34//! #[near(contract_state)]
35//! struct MyContract {}
36//! ```
37
38/// Generic hook trait for injecting code before and after component functions.
39pub trait Hook<C, A = ()> {
40 /// Execute a function with hooks.
41 fn hook<R>(contract: &mut C, _args: &A, f: impl FnOnce(&mut C) -> R) -> R {
42 f(contract)
43 }
44}
45
46impl<C, A> Hook<C, A> for () {}
47
48impl<C, A, T, U> Hook<C, A> for (T, U)
49where
50 T: Hook<C, A>,
51 U: Hook<C, A>,
52{
53 fn hook<R>(contract: &mut C, args: &A, f: impl FnOnce(&mut C) -> R) -> R {
54 T::hook(contract, args, |contract| U::hook(contract, args, f))
55 }
56}