Skip to content

Commit

Permalink
docs: document callback_unwrap attribute (#1313)
Browse files Browse the repository at this point in the history
* docs: document `callback_unwrap` attribute

Fixes #1303

Adds documentation for the `callback_unwrap` attribute under the `#[near]` macro docs, including:

- Description of functionality and usage
- Integration with `PromiseOrValue`
- Cross-contract factorial example 
- Error handling alternatives
- Rust doc annotations and references

Resolves #1303

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/near/near-sdk-rs/issues/1303?shareId=XXXX-XXXX-XXXX-XXXX).

* comment: resolves #1313 (comment)

* comment: resolves #1313 (comment)

* comment: resolves #1313 (comment)

* comment: resolves #1313 (comment)

* comment: resolves #1313 (comment)

* comment: resolves #1313 (comment)

* comment: resolves #1313 (comment)

* snapshot: fix compilation test schemars 0.8.21 -> 0.8.22

---------

Co-authored-by: dj8yf0μl <noreply@nowhere.org>
  • Loading branch information
TobieTom and dj8yf0μl authored Feb 25, 2025
1 parent 9596835 commit 746e428
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 52 deletions.
37 changes: 0 additions & 37 deletions near-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,43 +374,6 @@ pub fn ext_contract(attr: TokenStream, item: TokenStream) -> TokenStream {
}
}

// The below attributes a marker-attributes and therefore they are no-op.

/// `callback` is a marker attribute it does not generate code by itself.
#[proc_macro_attribute]
#[deprecated(since = "4.0.0", note = "Case is handled internally by macro, no need to import")]
pub fn callback(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}

/// `callback_args_vec` is a marker attribute it does not generate code by itself.
#[deprecated(since = "4.0.0", note = "Case is handled internally by macro, no need to import")]
#[proc_macro_attribute]
pub fn callback_vec(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}

/// `serializer` is a marker attribute it does not generate code by itself.
#[deprecated(since = "4.0.0", note = "Case is handled internally by macro, no need to import")]
#[proc_macro_attribute]
pub fn serializer(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}

/// `result_serializer` is a marker attribute it does not generate code by itself.
#[deprecated(since = "4.0.0", note = "Case is handled internally by macro, no need to import")]
#[proc_macro_attribute]
pub fn result_serializer(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}

/// `init` is a marker attribute it does not generate code by itself.
#[deprecated(since = "4.0.0", note = "Case is handled internally by macro, no need to import")]
#[proc_macro_attribute]
pub fn init(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}

#[cfg(feature = "abi")]
#[derive(darling::FromDeriveInput, Debug)]
#[darling(attributes(abi), forward_attrs(serde, borsh_skip, schemars, validate))]
Expand Down
2 changes: 1 addition & 1 deletion near-sdk/compilation_tests/schema_derive_invalids.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ error[E0277]: the trait bound `Inner: JsonSchema` is not satisfied
(T0, T1, T2, T3, T4, T5)
and $N others
note: required by a bound in `SchemaGenerator::subschema_for`
--> $CARGO/schemars-0.8.21/src/gen.rs
--> $CARGO/schemars-0.8.22/src/gen.rs
|
| pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
| ^^^^^^^^^^ required by this bound in `SchemaGenerator::subschema_for`
14 changes: 11 additions & 3 deletions near-sdk/src/environment/env.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
//! Blockchain-specific methods available to the smart contract that allow to interact with NEAR runtime.
//! This is a wrapper around a low-level [`near_sys`](near_sys). Unless you know what you are doing prefer using `env::*`
//! whenever possible. In case of cross-contract calls prefer using even higher-level API available
//! through `callback_args`, `callback_args_vec`, `ext_contract`, `Promise`, and `PromiseOrValue`.
//! This is a wrapper around a low-level [`near_sys`](near_sys).
//!
//! Unless you know what you are doing prefer using `env::*`
//! whenever possible.
//!
//! In case of cross-contract calls prefer using higher-level API available
//! through [`crate::Promise`], and [`crate::PromiseOrValue<T>`].
use std::convert::TryInto;
use std::mem::{size_of, size_of_val};
Expand Down Expand Up @@ -754,7 +758,9 @@ pub fn alt_bn128_pairing_check(value: &[u8]) -> bool {
/// ```
///
/// More info about promises in [NEAR documentation](https://docs.near.org/build/smart-contracts/anatomy/crosscontract#promises)
///
/// More low-level info here: [`near_vm_runner::logic::VMLogic::promise_create`]
///
/// Example usages of this low-level api are <https://github.com/near/near-sdk-rs/tree/master/examples/factory-contract/low-level/src/lib.rs> and <https://github.com/near/near-sdk-rs/blob/master/examples/cross-contract-calls/low-level/src/lib.rs>
///
pub fn promise_create(
Expand Down Expand Up @@ -810,6 +816,7 @@ pub fn promise_create(
/// );
/// ```
/// More low-level info here: [`near_vm_runner::logic::VMLogic::promise_then`]
///
/// Example usages of this low-level api are <https://github.com/near/near-sdk-rs/tree/master/examples/factory-contract/low-level/src/lib.rs> and <https://github.com/near/near-sdk-rs/blob/master/examples/cross-contract-calls/low-level/src/lib.rs>
pub fn promise_then(
promise_idx: PromiseIndex,
Expand Down Expand Up @@ -1466,6 +1473,7 @@ pub(crate) fn promise_result_internal(result_idx: u64) -> Result<(), PromiseErro
/// promise_return(promise);
/// ```
/// More low-level info here: [`near_vm_runner::logic::VMLogic::promise_return`]
///
/// Example usages: [one](https://github.com/near/near-sdk-rs/tree/master/examples/cross-contract-calls/low-level/src/lib.rs), [two](https://github.com/near/near-sdk-rs/tree/master/examples/factory-contract/low-level/src/lib.rs)
pub fn promise_return(promise_idx: PromiseIndex) {
unsafe { sys::promise_return(promise_idx.0) }
Expand Down
88 changes: 88 additions & 0 deletions near-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,94 @@ extern crate quickcheck;
/// }
/// ```
///
/// ## `#[callback_unwrap]` (annotates function arguments)
///
/// Automatically unwraps the successful result of a callback from a cross-contract call.
/// Used on parameters in callback methods that are invoked as part of a cross-contract call chain.
/// If the promise fails, the method will panic with the error message.
///
/// This attribute is commonly used with [`Promise`] or [`PromiseOrValue<T>`] as the return type of another contract method,
/// whose return value will be passed as argument to `#[callback_unwrap]`-annotated argument
///
/// ### Example with Cross-Contract Factorial:
///
/// In the example:
/// - lower level [`env::promise_create`], [`env::promise_then`] and [`env::promise_return`] are used in
/// `factorial` method to set up a callback of `factorial_mult` with result of factorial for `(n-1)`
/// - [`#[private]`](near#private-annotates-methods-of-a-type-in-its-impl-block) on `factorial_mult` is used to
/// to allow only calling `factorial_mult` from factorial contract method by `CrossContract` itself
/// and disallow for it to be called externally by users
///
/// ```rust
/// use near_sdk::{near, env, log, NearToken, Gas};
///
/// // Prepaid gas for a single (not inclusive of recursion) `factorial` call.
/// const FACTORIAL_CALL_GAS: Gas = Gas::from_tgas(20);
///
/// // Prepaid gas for a single `factorial_mult` call.
/// const FACTORIAL_MULT_CALL_GAS: Gas = Gas::from_tgas(10);
///
/// #[near(contract_state)]
/// #[derive(Default)]
/// pub struct CrossContract {}
///
/// #[near]
/// impl CrossContract {
/// pub fn factorial(&self, n: u32) {
/// if n <= 1 {
/// env::value_return(&serde_json::to_vec(&1u32).unwrap());
/// return;
/// }
/// let account_id = env::current_account_id();
/// let prepaid_gas = env::prepaid_gas().saturating_sub(FACTORIAL_CALL_GAS);
/// let promise0 = env::promise_create(
/// account_id.clone(),
/// "factorial",
/// &serde_json::to_vec(&(n - 1,)).unwrap(),
/// NearToken::from_near(0),
/// prepaid_gas.saturating_sub(FACTORIAL_MULT_CALL_GAS),
/// );
/// let promise1 = env::promise_then(
/// promise0,
/// account_id,
/// "factorial_mult",
/// &serde_json::to_vec(&(n,)).unwrap(),
/// NearToken::from_near(0),
/// FACTORIAL_MULT_CALL_GAS,
/// );
/// env::promise_return(promise1);
/// }
///
/// #[private]
/// pub fn factorial_mult(&self, n: u32, #[callback_unwrap] factorial_n_minus_one_result: u32) -> u32 {
/// log!("Received n: {:?}", n);
/// log!("Received factorial_n_minus_one_result: {:?}", factorial_n_minus_one_result);
///
/// let result = n * factorial_n_minus_one_result;
///
/// log!("Multiplied {:?}", result.clone());
/// result
/// }
/// }
/// ```
/// which has the following lines in a `factorial`'s view call log:
///
/// ```bash,ignore
/// logs: [
/// "Received n: 5",
/// "Received factorial_n_minus_one_result: 24",
/// "Multiplied 120",
/// ],
/// ```
///
/// ### Other examples within repo:
///
/// - `Cross-Contract Factorial` again [examples/cross-contract-calls](https://github.com/near/near-sdk-rs/blob/9596835369467cac6198e8de9a4b72a38deee4a5/examples/cross-contract-calls/high-level/src/lib.rs?plain=1#L26)
/// - same example as [above](near#example-with-cross-contract-factorial), but uses [`Promise::then`] instead of [`env`](mod@env) host functions calls to set up a callback of `factorial_mult`
/// - [examples/adder](https://github.com/near/near-sdk-rs/blob/9596835369467cac6198e8de9a4b72a38deee4a5/examples/adder/src/lib.rs?plain=1#L30)
/// - [examples/adder](https://github.com/near/near-sdk-rs/blob/9596835369467cac6198e8de9a4b72a38deee4a5/examples/adder/src/lib.rs?plain=1#L31)
/// - [examples/callback-results](https://github.com/near/near-sdk-rs/blob/9596835369467cac6198e8de9a4b72a38deee4a5/examples/callback-results/src/lib.rs?plain=1#L51)
///
/// ## `#[near(event_json(...))]` (annotates enums)
///
/// By passing `event_json` as an argument `near` will generate the relevant code to format events
Expand Down
27 changes: 16 additions & 11 deletions near-sdk/src/near_annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
//! This is not a real module; here we document the attributes that [`#[near]`](crate::near)
//! and [`#[near_bindgen]`](crate::near_bindgen) macro use.
//!
//! `near_bindgen` and `near_sdk` shares almost the same attributes:
//! * init
//! * payable
//! * private
//! * handle_result
//! * event_json
//! * contract_metadata
//! * serializer
//! `near_bindgen` and `near_sdk` share most of the attributes:
//! * `init`
//! * `payable`
//! * `private`
//! * `handle_result`
//! * `callback_unwrap`
//! * `event_json`
//! * `contract_metadata`
//! * `serializer`
//! * `result_serializer`
//!
//! These attributes are only part of the `near` macro.
//! * serializers
//! * contract_state
//! Following attributes are only part of the `near` macro:
//! * `serializers`
//! * `contract_state`
/// See [`near_sdk::near #[init]`](crate::near#init-annotates-methods-of-a-type-in-its-impl-block)
pub fn init() {}
Expand All @@ -31,6 +33,9 @@ pub fn result_serializer() {}
/// See [`near_sdk::near #[handle_result]`](crate::near#handle_result-annotates-methods-of-a-type-in-its-impl-block)
pub fn handle_result() {}

/// See [`near_sdk::near #[callback_unwrap]`](crate::near#callback_unwrap-annotates-function-arguments)
pub fn callback_unwrap() {}

/// See [`near_sdk::near #[near(event_json(...))]`](crate::near#nearevent_json-annotates-enums)
pub fn event_json() {}

Expand Down

0 comments on commit 746e428

Please sign in to comment.