Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: impl details of #[callback_unwrap] #1319

Closed
wants to merge 10 commits into from
14 changes: 10 additions & 4 deletions examples/adder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,16 @@ impl Adder {
&self,
#[callback_unwrap] a: DoublePair,
#[callback_unwrap] b: DoublePair,
#[callback_vec] others: Vec<DoublePair>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[callback_vec] isn't really compatible with #[callback_unwrap] to be used in this manner, due to a feature??/bug??, because it revisits the same promise indices, already visited by callback_unwrap,
so others vector in this example contained a and b as first elements

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created #1320 to describe with an example + assertion of current behaviour, because it looks like it's not obvious to get it with cargo expand example

) -> DoublePair {
Some(b).iter().chain(others.iter()).fold(a, |acc, el| DoublePair {
Some(b).iter().fold(a, |acc, el| DoublePair {
first: sum_pair(&acc.first, &el.first),
second: sum_pair(&acc.second, &el.second),
})
}

pub fn add_callback_vec(&self, #[callback_vec] elements: Vec<DoublePair>) -> DoublePair {
let start = DoublePair { first: Pair(0, 0), second: Pair(0, 0) };
elements.iter().fold(start, |acc, el| DoublePair {
first: sum_pair(&acc.first, &el.first),
second: sum_pair(&acc.second, &el.second),
})
Expand All @@ -55,8 +62,7 @@ mod tests {

let res = contract.view("__contract_abi").await?;

let abi_root =
serde_json::from_slice::<AbiRoot>(&zstd::decode_all(&res.result[..])?)?;
let abi_root = serde_json::from_slice::<AbiRoot>(&zstd::decode_all(&res.result[..])?)?;

assert_eq!(abi_root.schema_version, "0.4.0");
assert_eq!(abi_root.metadata.name, Some("adder".to_string()));
Expand Down
16 changes: 14 additions & 2 deletions near-sdk/src/environment/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,7 @@ pub fn promise_batch_action_delete_account(
/// assert_eq!(promise_results_count(), 0);
/// ```
/// More low-level info here: [`near_vm_runner::logic::VMLogic::promise_results_count`]
///
/// See example of usage [here](https://github.com/near/near-sdk-rs/blob/master/examples/cross-contract-calls/low-level/src/lib.rs)
pub fn promise_results_count() -> u64 {
unsafe { sys::promise_results_count() }
Expand Down Expand Up @@ -1430,8 +1431,14 @@ pub fn promise_results_count() -> u64 {
/// }
/// };
/// ```
///
/// More low-level info here: [`near_vm_runner::logic::VMLogic::promise_result`]
/// Example usages: [one](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/near-contract-standards/src/fungible_token/core_impl.rs#L178), [two](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/near-contract-standards/src/non_fungible_token/core/core_impl.rs#L433), [three](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/examples/factory-contract/low-level/src/lib.rs#L61), [four](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/examples/cross-contract-calls/low-level/src/lib.rs#L46)
///
/// Example usages:
/// - [near-contract-standards/src/fungible_token](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/near-contract-standards/src/fungible_token/core_impl.rs#L178)
/// - [near-contract-standards/src/non_fungible_token](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/near-contract-standards/src/non_fungible_token/core/core_impl.rs#L433)
/// - [examples/factory-contract/low-level](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/examples/factory-contract/low-level/src/lib.rs#L61)
/// - [examples/cross-contract-calls/low-level](https://github.com/near/near-sdk-rs/blob/189897180649bce47aefa4e5af03664ee525508d/examples/cross-contract-calls/low-level/src/lib.rs#L46)
pub fn promise_result(result_idx: u64) -> PromiseResult {
match promise_result_internal(result_idx) {
Ok(()) => {
Expand Down Expand Up @@ -1806,8 +1813,10 @@ pub fn storage_write(key: &[u8], value: &[u8]) -> bool {
/// Reads the value stored under the given key.
///
/// # Use cases
///
/// Storage functions are typically used to upgrade/migrate a contract state, preventing errors like `Cannot deserialize the contract state` after rolling out the breaking changes to the network.
/// For practical examples, see different implementations in [this repository](https://github.com/near-examples/update-migrate-rust).
///
/// For practical examples, see different implementations in [`near-examples/update-migrate-rust` repo](https://github.com/near-examples/update-migrate-rust).
///
/// # Examples
/// ```
Expand All @@ -1817,6 +1826,9 @@ pub fn storage_write(key: &[u8], value: &[u8]) -> bool {
/// storage_write(b"key", b"value");
/// assert_eq!(storage_read(b"key").unwrap(), b"value");
/// ```
///
/// Another example:
/// - [near-contract-standards/src/upgrade](https://github.com/near/near-sdk-rs/blob/746e4280a7e25b2036bd4e2f2c186cd76e1a7cde/near-contract-standards/src/upgrade/mod.rs?plain=1#L77)
pub fn storage_read(key: &[u8]) -> Option<Vec<u8>> {
match unsafe { sys::storage_read(key.len() as _, key.as_ptr() as _, ATOMIC_OP_REGISTER) } {
0 => None,
Expand Down
39 changes: 39 additions & 0 deletions near-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,45 @@ extern crate quickcheck;
/// - [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)
///
/// ### Implementation details of `#[callback_unwrap]` macro and **host functions** calls used
///
/// ```rust
/// # use near_sdk::near;
/// # #[near(contract_state)]
/// # pub struct Contract { /* .. */ }
/// #[near]
/// impl Contract {
/// pub fn method(
/// &mut self,
/// regular: String,
/// #[callback_unwrap] one: String,
/// #[callback_unwrap] two: String
/// ) { /* .. */ }
/// }
/// ```
///
/// For above `method` using the attribute on arguments, changes the body of function generated in [`#[near]` on mutating method](near#for-above-mutating-method-near-macro-defines-the-following-function)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this pr depends on #1307, as the doc anchor is first added there.
the broken link is not detected by

        RUSTDOCFLAGS: -D warnings
      run: |
        cargo doc -p near-sdk --features unstable,legacy,unit-testing,__macro-docs,__abi-generate

rust-lang/rust#137740

///
/// ```rust,no_run
/// #[no_mangle]
/// pub extern "C" fn method() { /* .. */ }
/// ```
///
/// in the following way:
///
/// 1. arguments, annotated with `#[callback_unwrap]`, are no longer expected to be included into `input`,
/// deserialized in (step **3**, [`#[near]` on mutating method](near#for-above-mutating-method-near-macro-defines-the-following-function)).
/// 2. for each argument, annotated with `#[callback_unwrap]`:
/// 1. [`env::promise_result`] host function is called with corresponding index, starting from 0
/// (`0u64` for argument `one`, `1u64` for argument `two` above), and saved into `promise_result` variable
/// 2. if the `promise_result` is a [`PromiseResult::Failed`] error, then [`env::panic_str`] host function is called to signal callback computation error
/// 3. otherwise, if the `promise_result` is a [`PromiseResult::Successful`], it's unwrapped and saved to a `data` variable
/// 4. `data` is deserialized similar to that as usual (step **3**, [`#[near]` on mutating method](near#for-above-mutating-method-near-macro-defines-the-following-function)),
/// and saved to `deserialized_n_promise` variable
/// 3. counterpart of (step **7**, [`#[near]` on mutating method](near#for-above-mutating-method-near-macro-defines-the-following-function)):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make it more compact:
2.1

[`env::promise_result`] host function is called with corresponding index

2.2

if the `promise_result` is a [`PromiseResult::Failed`] error, then [`env::panic_str`] is called

2.3 can be removed

2.4 otherwise, promise_result is deserialized ...

But your variant is also lgtm

/// original method is called `Contract::method(&mut state, deserialized_input_success.regular, deserialized_0_promise, deserialized_1_promise)`,
/// as defined in `#[near]` annotated impl block
///
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this section is roughly based on the following expansion diff:

142c142
<     pub fn factorial_mult(self, n: u32, cur: u32) -> ::near_sdk::Promise {
---
>     pub fn factorial_mult(self, n: u32) -> ::near_sdk::Promise {
147d146
<                 cur: &'nearinput u32,
165c164
<                             false as usize + 1 + 1,
---
>                             false as usize + 1,
172,176d170
<                         _serde::ser::SerializeStruct::serialize_field(
<                             &mut __serde_state,
<                             "cur",
<                             &self.cur,
<                         )?;
181c175
<             let __args = Input { n: &n, cur: &cur };
---
>             let __args = Input { n: &n };
459d452
<         cur: u32,
477d469
<                     __field1,
503d494
<                             1u64 => _serde::__private::Ok(__Field::__field1),
516d506
<                             "cur" => _serde::__private::Ok(__Field::__field1),
529d518
<                             b"cur" => _serde::__private::Ok(__Field::__field1),
582,595c571
<                                         &"struct Input with 2 elements",
<                                     ),
<                                 );
<                             }
<                         };
<                         let __field1 = match _serde::de::SeqAccess::next_element::<
<                             u32,
<                         >(&mut __seq)? {
<                             _serde::__private::Some(__value) => __value,
<                             _serde::__private::None => {
<                                 return _serde::__private::Err(
<                                     _serde::de::Error::invalid_length(
<                                         1usize,
<                                         &"struct Input with 2 elements",
---
>                                         &"struct Input with 1 element",
600,603c576
<                         _serde::__private::Ok(Input {
<                             n: __field0,
<                             cur: __field1,
<                         })
---
>                         _serde::__private::Ok(Input { n: __field0 })
614d586
<                         let mut __field1: _serde::__private::Option<u32> = _serde::__private::None;
629,638d600
<                                 __Field::__field1 => {
<                                     if _serde::__private::Option::is_some(&__field1) {
<                                         return _serde::__private::Err(
<                                             <__A::Error as _serde::de::Error>::duplicate_field("cur"),
<                                         );
<                                     }
<                                     __field1 = _serde::__private::Some(
<                                         _serde::de::MapAccess::next_value::<u32>(&mut __map)?,
<                                     );
<                                 }
652,661c614
<                         let __field1 = match __field1 {
<                             _serde::__private::Some(__field1) => __field1,
<                             _serde::__private::None => {
<                                 _serde::__private::de::missing_field("cur")?
<                             }
<                         };
<                         _serde::__private::Ok(Input {
<                             n: __field0,
<                             cur: __field1,
<                         })
---
>                         _serde::__private::Ok(Input { n: __field0 })
665c618
<                 const FIELDS: &'static [&'static str] = &["n", "cur"];
---
>                 const FIELDS: &'static [&'static str] = &["n"];
678c631
<     let Input { n, cur }: Input = match ::near_sdk::env::input() {
---
>     let Input { n }: Input = match ::near_sdk::env::input() {
687a641,648
>     };
>     let data: ::std::vec::Vec<u8> = match ::near_sdk::env::promise_result(0u64) {
>         ::near_sdk::PromiseResult::Successful(x) => x,
>         _ => ::near_sdk::env::panic_str("Callback computation 0 was not successful"),
>     };
>     let cur: u32 = match ::near_sdk::serde_json::from_slice(&data) {
>         Ok(deserialized) => deserialized,
>         Err(_) => ::near_sdk::env::panic_str("Failed to deserialize callback using JSON"),

/// ## `#[near(event_json(...))]` (annotates enums)
///
/// By passing `event_json` as an argument `near` will generate the relevant code to format events
Expand Down
22 changes: 16 additions & 6 deletions near-sdk/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,28 @@
}

/// Returns true if promise was successful.
/// Fails if called outside a callback that received 1 promise result.
/// Uses low-level [`crate::env::promise_results_count`].
///
/// Calls [`crate::env::panic_str`] **host function** if called outside a callback that received precisely 1 promise result.
///
/// Uses low-level [`crate::env::promise_results_count`] and [`crate::env::promise_result`] **host functions**.
pub fn is_promise_success() -> bool {
require!(env::promise_results_count() == 1, "Contract expected a result on the callback");
require!(
env::promise_results_count() == 1,

Check warning on line 98 in near-sdk/src/utils/mod.rs

View check run for this annotation

Codecov / codecov/patch

near-sdk/src/utils/mod.rs#L97-L98

Added lines #L97 - L98 were not covered by tests
"Contract expected a single result on the callback"
);
env::promise_result_internal(0).is_ok()
}

/// Returns the result of the promise if successful. Otherwise returns None.
/// Fails if called outside a callback that received 1 promise result.
/// Uses low-level [`crate::env::promise_results_count`] and [`crate::env::promise_result`].
///
/// Calls [`crate::env::panic_str`] **host function** if called outside a callback that received precisely 1 promise result.
///
/// Uses low-level [`crate::env::promise_results_count`] and [`crate::env::promise_result`] **host functions**.
pub fn promise_result_as_success() -> Option<Vec<u8>> {
require!(env::promise_results_count() == 1, "Contract expected a result on the callback");
require!(
env::promise_results_count() == 1,

Check warning on line 111 in near-sdk/src/utils/mod.rs

View check run for this annotation

Codecov / codecov/patch

near-sdk/src/utils/mod.rs#L110-L111

Added lines #L110 - L111 were not covered by tests
"Contract expected a single result on the callback"
);
match env::promise_result(0) {
PromiseResult::Successful(result) => Some(result),
_ => None,
Expand Down
Loading