Callbacks
NEAR Protocol is a sharded, proof-of-stake blockchain that behaves differently than proof-of-work blockchains. When interacting with a native Rust (compiled to Wasm) smart contract, cross-contract calls are asynchronous. Callbacks are used to either get the result of a cross-contract call or tell if a cross-contract call has succeeded or failed.
There are two techniques to write cross-contract calls: high-level and low-level. This document will mostly focus on the high-level approach. There are two examples in the Rust SDK repository that demonstrate these, as linked above. Note that these examples use cross-contract calls "to itself." We'll show two examples demonstrating the high-level approach.
#
Calculator exampleThere is a helper macro that allows you to make cross-contract calls with the syntax #[ext_contract(...)]
. It takes a Rust Trait and converts it to a module with static methods. Each of these static methods takes positional arguments defined by the Trait, then the receiver_id
, the attached deposit and the amount of gas and returns a new Promise
.
For example, let's define a calculator contract Trait:
It's equivalent to the following code:
Let's assume the calculator is deployed on calc.near
, we can use the following:
#
Whitelist exampleNext we'll look at a simple cross-contract call is made to a whitelist smart contract, returning whether an account is in the whitelist or not.
The common pattern with cross-contract calls is to call a method on an external smart contract, use .then
syntax to specify a callback, and then retrieve the result or status of the promise. The callback will typically live inside the same, calling smart contract. There's a special macro used for the callback function, which is #[private]. We'll see this pattern in the example below.
The following example demonstrates two common approaches to callbacks using the high-level cross-contract approach. When writing high-level cross-contract calls, special traits are set up as interfaces for the smart contract being called, and (typically) the current contract doing the calling (where callback logic will live). The second trait is, by convention, referred to as ext_self
. Here are the two traits used in a simple example:
After creating these traits, we'll show two simple functions that will make a cross-contract call to a whitelist smart contract, asking if the account mike.testnet
is whitelisted. These methods will both return true
using different approaches. First we'll look at the methods, then we'll look at the differences in callbacks. Note that for simplicity in this example, the values are hardcoded.
The syntax begins with ext_whitelist::is_whitelisted(…
showing that we're using the trait to call the method is_whitelisted
. However, the trait shows only one argument, but we've included four. The last three arguments are used behind the scenes and represent:
- The target smart contract account. (In this case,
whitelist.demo.testnet
.) - An attached deposit of Ⓝ, in yoctoⓃ. (1 Ⓝ = 1000000000000000000000000 yoctoⓃ.)
- The amount of gas.
The two methods in the snippet above are very similar, except they will call separate callbacks in the smart contract, callback_promise_result
and callback_arg_macro
.
These two callbacks show how a value can be obtained. The first method gets the value from the promise result, while the second uses a macro on the argument to cast the value into what's desired. Please note that at this time, the second approach may not catch errors as well as the first approach. See this issue for more details.
The biggest difference between these two approaches is how the arguments are defined (or absent) in the traits shown earlier.
And that's it! Understanding how to make a cross-contract call and receive a result is an important part of developing smart contracts on NEAR. Two interesting references for using cross-contract calls can be found in the fungible token and non-fungible token examples.