Cross-Contract Calls
Cross-contract calls allow your contract to interact with other deployed contracts. This is useful for:
- Querying information from another contract.
- Executing a method in another contract.
Cross-Contract Calls are Independent
The method in which you make the call and the method in which you receive the result are different.
Cross-Contract Calls are Asynchronous
There is a delay between the call and the callback in which everyone can still interact with your contract.
Snippet: Querying Information
While making your contract, it is likely that you will want to query information from another contract. Below, you can see a basic example in which we query the greeting message from our Hello NEAR example.
- 🌐 JavaScript
- 🦀 Rust
- 🚀 AssemblyScript
loading...
- lib.rs
- external.rs
loading...
loading...
- index.ts
- external.ts
loading...
loading...
Snippet: Sending Information
Calling another contract passing information is also a common scenario. Bellow you can see a method that interacts with the Hello NEAR example to change its greeting message.
- 🌐 JavaScript
- 🦀 Rust
- 🚀 AssemblyScript
loading...
- lib.rs
- external.rs
loading...
loading...
- index.ts
- external.ts
loading...
loading...
Promises
Cross-contract calls work by creating two promises in the network:
- A promise to execute code in the external contract (
Promise.create
). - A promise to call back a different method in your contract with the result (
Promise.then
).
Both promises take the same arguments:
- 🌐 JavaScript
- 🦀 Rust
- 🚀 AssemblyScript
NearPromise.new("external_address").functionCall("method", bytes(JSON.stringify(arguments)), DEPOSIT, GAS);
external_trait::ext("external_address") .with_attached_deposit(DEPOSIT) .with_static_gas(GAS) .method(arguments);
ContractPromise.create( "external_address", "method", "encoded_arguments", GAS, DEPOSIT )
- The address of the contract you want to interact with
- The method that you want to execute
- The (encoded) arguments to pass to the method
- The amount of GAS to use (deducted from the attached Gas)
- The amount of NEAR to attach (deducted from your contract’s balance)
Notice that the callback could be made to any contract. This means that, if you want, the result could be potentially handled by another contract.
The fact that you are creating a Promise means that both the cross-contract call and callback will not execute immediately. In fact:
- The cross-contract call will execute 1 or 2 blocks after your method finishes correctly.
- The callback will then execute 1 or 2 blocks after the external method finishes (correctly or not)
Callback Method
If your method finishes correctly, then eventually your callback method will execute. This will happen whether the external contract finishes successfully or not. We repeat, if your original method finishes correctly, then your callback will always execute.
In the callback method you will have access to the result, which contains two important arguments:
status
: Telling if the external method finished successfully or notbuffer
: Having the value returned by the external method (if any)
The callback methods in your contract must be public, so it can be called when the second promise executes. However, they should be only callable by your contract. Always make sure to make it private by asserting that the predecessor
is current_account_id
. In rust this can be achieved using the #[private]
decorator.
Checking Execution Status
- 🌐 JavaScript
- 🦀 Rust
- 🚀 AssemblyScript
loading...
loading...
- index.ts
- external.ts
loading...
loading...
Successful Execution
In case the call finishes successfully, the resulting object will have a status
of 1, and the buffer
will have the encoded result (if any). In order to recover the result you need to decode it from the resulting buffer
:
- 🌐 JavaScript
- 🦀 Rust
- 🚀 AssemblyScript
loading...
loading...
loading...
Failed Execution
If the external method fails (i.e. it panics), then your callback will be executed anyway. Here you need to manually rollback any changes made in your contract during the original call. Particularly:
- If the contract attached NEAR to the call, the funds are sent back to the contract's account.
- If the original method made any state changes (i.e. changed or stored data), they won't be automatically reverted.
If your original method finishes correctly then the callback executes even if the external method panics. Your state will not rollback automatically, and $NEAR will not be returned to the signer automatically. Always make sure to check in the callback if the external method failed, and manually rollback any operation if necessary.
Security Concerns
While writing cross-contract calls there is a significant aspect to keep in mind: all the calls are independent and asynchronous. In other words:
- The method in which you make the call and method for the callback are independent.
- There is a delay between the call and the callback, in which people can still interact with the contract
This has important implications on how you should handle the callbacks. Particularly:
- Make sure you don't leave the contract in a exploitable state between the call and the callback.
- Manually rollback any changes to the state in the callback if the external call failed.
We have a whole security section dedicated to these specific errors, so please go and check it.
Not following these basic security guidelines could expose your contract to exploits. Please check the security section, and if still in doubt, join us in Discord.