Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions docs/building-apps/developer-tools/cdks/rust/intercanister.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ This example will demonstrate how to call an example counter canister, which pro
3. Setting the counter to an arbitrary value.
4. Setting the counter to an arbitrary value while returning the previous value.

:::note
The Internet Computer supports two types of inter-canister calls: bounded- and unbounded-wait calls. This tutorial shows both, but in general, we recommend to use bounded-wait calls wherever possible for scalability and robustness reasons.
:::

## Prerequisites

To follow along, clone the [Git examples repository](https://github.com/dfinity/examples). The code for this tutorial is in the `rust/inter-canister-calls` directory, where you can find the completed code in the `caller` package. Note that this example uses the alpha version of the Rust CDK.
Expand Down Expand Up @@ -177,42 +181,40 @@ The Rust CDK provides helpful functions such as `is_clean_reject` and `is_immedi
To summarize, bounded-wait calls will time out after a while, but you might not know whether the callee executed the call or not. Use them for reads or endpoints that are idempotent or provide a way to query their results.
:::

## Attaching cycles: canister signatures
## Attaching cycles: depositing cycles

A canister can attach [cycles](/docs/building-apps/getting-started/tokens-and-cycles) (Internet Computer "gas") to any call that they make, transferring cycles from the caller's to the callee's cycle balance. The callee must explicitly [accept](https://docs.rs/ic-cdk/latest/ic_cdk/api/call/fn.msg_cycles_accept128.html) such cycles; non-accepted cycles are refunded to the caller.

Cycle transfers are generally either used to pay for the callee's costs of processing the call or to move and store cycles as assets. An example where they are used to pay for call processing costs is the IC [threshold signature feature](/docs/building-apps/network-features/signatures/t-ecdsa), which allows a canister to hold a cryptographic key and sign messages with it. An example of cycles being used as assets is the [cycles ledger](/docs/defi/token-ledgers/cycles-ledger/).
Moreover, the management canister provides a special method called `deposit_cycles`, which takes a target canister as a parameter. When called, `deposit_cycles` simply accepts all the attached cycles and deposits them to the target canister's balance.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: A "parameter" is a part of the function's signature (e.g. target: CanisterId); the value passed in a function call is an "argument". I believe the latter is more appropriate here, as this description is from the point of view of the dev / client.

Suggested change
Moreover, the management canister provides a special method called `deposit_cycles`, which takes a target canister as a parameter. When called, `deposit_cycles` simply accepts all the attached cycles and deposits them to the target canister's balance.
Moreover, the management canister provides a special method called `deposit_cycles`, which takes a target canister as an argument. When called, `deposit_cycles` simply accepts all the attached cycles and deposits them to the target canister's balance.


Below is a threshold signature example that shows how to attach cycles to a call:
Below is an example that shows how to attach cycles to a `deposit_cycles` call.

```rust reference
https://github.com/dfinity/examples/blob/945e77d5f7f91dd1d8357b4b7cbc3a11708e8803/rust/inter-canister-calls/src/caller/src/lib.rs#L138-L187
https://github.com/dfinity/examples/blob/1d91ffa675699a78f0f31603f13d2e8d7149480c/rust/inter-canister-calls/src/caller/src/lib.rs#L139-L153
```

Let's test this by calling the `sign_message` method. The exact signature in your response will differ, as it will based on a secret key from your local development environment.
Let's test this by instructing `send_cycles` to send two billion of the caller's available cycles to the counter.

```bash
dfx canister call caller sign_message '("Some text to be signed")'
(
variant {
Ok = "394b7250ab7a088238fae3af9b60fec521584ac8c874b2ca72e6b950cda509452f307006693c342636afb5b2cb7a395106cc04365bd499c0064f5842a0bdbe2f"
},
)
% dfx canister status counter | grep Balance
Balance: 4_560_238_047_511 Cycles
% dfx canister call caller send_cycles "( principal \"`dfx canister id counter`\", 2_000_000_000: nat64 )"
(variant { Ok })
% dfx canister status counter | grep Balance
Balance: 4_562_236_725_265 Cycles
```

Cycles can be attached to both bounded and unbounded wait messages. For unbounded wait messages, cycles that are not consumed by the callee are guaranteed to be refunded to the caller. As the example notes, refunds do not happen for bounded-wait calls that result in an error with the `SysUnknown` reject code, which is the code issued when the system decides to stop waiting for a response.
The balances you see will obviously differ. As you see, here the counter's balance has increased by a little less than 2 billion; the difference is used up by the running costs of the counter, which are continually charged while the canister is active.
Copy link
Contributor

Choose a reason for hiding this comment

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

"counter canister"?

Suggested change
The balances you see will obviously differ. As you see, here the counter's balance has increased by a little less than 2 billion; the difference is used up by the running costs of the counter, which are continually charged while the canister is active.
The balances you see will obviously differ. As you see, here the counter's balance has increased by a little less than 2 billion; the difference is used up by the running costs of the counter canister, which are continually charged while the canister is active.


However, this is usually acceptable for API calls that charge for cycles, since the amount charged is usually low (10 billion cycles for signatures with the test key). For transferring larger amounts of cycles, switch to using unbounded wait calls. See the section on [inter-canister calls](/docs/references/async-code) for more details.
Cycles can be attached to both bounded and unbounded wait calls. In case of an error, where the call doesn't make it to the callee, the cycles will eventually be refunded. For bounded-wait calls, this may happen asynchronously, after the error is returned.

:::info
To summarize, you can transfer cycles to the callee by attaching them to a bounded- or unbounded-wait call. Bounded-wait calls may drop the attached cycles, so avoid using them for large cycle amounts.
:::
In general, bounded-wait calls are sufficient and recommended for cycle deposits. However, since there isn't a bulletproof way to check whether `deposit_cycles` succeeded or not in case of a `SYS_UNKNOWN`, applications which need to do error handling on `deposit_cycles` calls (for examples, DEXes which trade wrapped cycles) may want to use unbounded-wait calls instead.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: "which" -> "that"

Suggested change
In general, bounded-wait calls are sufficient and recommended for cycle deposits. However, since there isn't a bulletproof way to check whether `deposit_cycles` succeeded or not in case of a `SYS_UNKNOWN`, applications which need to do error handling on `deposit_cycles` calls (for examples, DEXes which trade wrapped cycles) may want to use unbounded-wait calls instead.
In general, bounded-wait calls are sufficient and recommended for cycle deposits. However, since there isn't a bulletproof way to check whether `deposit_cycles` succeeded or not in case of a `SYS_UNKNOWN`, applications that need to do error handling on `deposit_cycles` calls (for examples, DEXes that trade wrapped cycles) may want to use unbounded-wait calls instead.


## Further reading

More details on how inter-canister calls execute and how the different call types work is provided in the documentation on [inter-canister calls and async code](/docs/references/async-code). Also consult the documentation on [properties of call execution](/docs/references/message-execution-properties) and [security best practices](/docs/building-apps/security/security-best-practices/inter-canister-calls). To allow your callers to handle call errors robustly, follow the [best practice](/docs/building-apps/best-practices/idempotency) document on retries and idempotency.

As noted in the above examples, an `update` method can always call any method of any other canister. In cases where you only need to call query methods on other canisters, and if you are sure that these canisters are on the same [subnet](https://learn.internetcomputer.org/hc/en-us/articles/34209955782420-Subnet-Creation) as your canister, you can also use [composite query calls](/docs/building-apps/interact-with-canisters/query-calls) methods instead of update methods.

For a real-life example of how to handle errors when calling canisters, see the [ICRC-1 examples](/docs/defi/token-ledgers/usage/icrc1_ledger_usage)
For a real-life example of how to handle errors when calling canisters, see the [ICRC-1 examples](/docs/defi/token-ledgers/usage/icrc1_ledger_usage). The [`ic-call-chaos`](https://crates.io/crates/ic-call-chaos) library helps test the correctness of error handling in your code. The [`ic-call-retry`](https://crates.io/crates/ic-call-retry) library provides retries on errors. The [`ic-safe-upgrades`](https://crates.io/crates/ic-safe-upgrades) library implements error handling when trying to upgrade canisters from other canisters.

6 changes: 1 addition & 5 deletions docs/references/async-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ Under the hood, the CDKs use the low-level Wasm API to perform inter-canister ca

[Cycles](/docs/current/developer-docs/gas-cost) are the currency in which canisters pay for their resource usage. Canisters can send some of the cycles they hold to other canisters. This can be done either by directly attaching cycles to any call to the target canister, or by calling the dedicated [`deposit_cycles`](/docs/references/ic-interface-spec/#ic-deposit_cycles) method of the management canister and attaching the cycles there. In the former way of attaching cycles (not using `deposit_cycles`), the target canister must explicitly accept part or all of the sent cycles when processing the call; the remainder is refunded to the caller.

:::info
Note that cycles may get dropped when using bounded-wait calls. See the section on [bounded- vs unbounded-wait calls](#ic-call-types) for more details.
:::

Some endpoints require the caller to attach cycles to the call. For example, the [threshold signature](/docs/current/developer-docs/smart-contracts/signatures/signing-messages-t-ecdsa) operations of the management canister require cycles to be attached. Requiring cycles can be used to implement a "direct gas" model, as opposed to the default ["reverse gas"](/docs/current/developer-docs/gas-cost) model of ICP.

Refer to the language documentation ([Motoko](/docs/current/motoko/main/writing-motoko/intercanister-calls), [Rust](/docs/current/developer-docs/backend/rust/intercanister), [Typescript](/docs/current/developer-docs/backend/typescript/), [Python](/docs/current/developer-docs/backend/python/)) for details on how to send and accept cycles.
Expand All @@ -75,7 +71,7 @@ Here are some guidelines how to choose between the two types of calls:

* Always prefer bounded-wait calls for calls that don't change the state of the callee, i.e., reads.
* For endpoints that change the state of the callee, the best practice is to make such endpoints amenable to [safe retries](/docs/building-apps/best-practices/idempotency) (e.g., by making them idempotent). Note that making user-facing endpoints amenable to safe retries is a good idea anyway, as it's needed to safely handle external user calls. Use bounded-wait calls for such endpoints, and handle the additional edge cases.
* Use unbounded-wait calls for endpoints that mutate state and do not enable safe retries, or calls that perform larger cycle transfers. As mentioned, bounded-wait calls lose cycles attached to requests/responses that are dropped. Be aware of the limitations of unbounded-wait calls listed above. If safe upgrades are needed, consider using a stateless proxy canister (see the [security best practices](https://internetcomputer.org/docs/current/developer-docs/security/security-best-practices/inter-canister-calls) for more information).
* Use unbounded-wait calls for endpoints that mutate state and do not enable safe retries. Be aware of the limitations of unbounded-wait calls listed above. If safe upgrades of the caller are needed, consider using a stateless proxy canister (see the [security best practices](https://internetcomputer.org/docs/current/developer-docs/security/security-best-practices/inter-canister-calls) for more information).

## `async/await` syntax, concurrency and state changes

Expand Down
Loading