This starter is a collection of helpful patterns, tooling and best practices for developing canisters in Rust. You can read more below.
- dfx 0.24.3
- node 22.12.0
- npm 10.9.0
pocket-ic
binary present in/usr/local/bin/pocket-ic
(make sure it's executable)
The canister's lifecycle methods are managed in src/backend/src/lifecycle.rs
. The init
method in main.rs
is called when the canister is created, and the post_upgrade
method in main.rs
is called after the canister is upgraded. Currently they both share the same implementation, that is when upgrading the canister you have to provide the same arguments as when creating the canister.
The application has a state that is stored in the canister. The state is a struct that is defined in src/backend/src/state.rs
. The state state is lost when the canister is being upgraded. For data that should be persisted, use refer to the Storage
section.
Data that is supposed to survive canister upgrades is defined in src/backend/src/storage.rs
. The data is stored in the canister's stable memory and is not lost when the canister is being upgraded. This is faciliated by using the ic-stable-structures
crate released by DFINITY.
There are two levels of logging in the application:
- INFO: General information about the application
- DEBUG: Detailed information about the application
To emit a log message, use the following syntax:
log!(INFO, "Received a request to fetch logs");
log!(DEBUG, "Received a request to fetch logs");
Logs in query calls are not persisted.
You can access canister logs via http requests to the canister's /logs
endpoint. It accepts the following query parameters:
priority
: The log level to filter by. Possible values areinfo
anddebug
.time
: Accepts a timestamp in nanoseconds. Only logs after this timestamp will be returned.sort
: The order in which logs are returned. Possible values areasc
anddesc
. Default isasc
whentime
is not provided, anddesc
otherwise.
Note that logs are not persisted and are lost when the canister is being upgraded. You also can't log when the execution traps, as by design the state changes are rolled back. For this you can rely on the canister logging feature provided by the protocol by simply using println!
exposed by the ic_cdk
, note that here you only have one level of logging and 4KB of log storage. Read more here and in the example here.
There are different metrics exposed via http requests to the canister's /metrics
endpoint. You can modify them in src/backend/src/metrics.rs
The application has a dashboard that can be accessed via http requests to the canisters the /dashboard
endpoint. It currently only exposes the way the user is greeted when calling greet
. You can modify the askama
dashboard template in src/backend/dashboard.rs
and the corresponding HTML in src/backend/templates/dashboard.html
.
We use a StableLog
to persist all greeting in stable memory. Usually this is used to store state changing events that should survive canister upgrades. They can be used to restore the canisters state that lives on the heap after an upgrade. You can learn more about the reasoning for this approach here. It can also be used as an audit trail for the canister. In our case we just replay the event logs in the post_upgrade
to restore a hashmap that keeps the count of greetings per name greeted.
canbench is a tool for benchmarking canisters on the Internet Computer. The config can be found in canbench.yml
.
To encode the deploy arguments in candid use the didc
tool:
didc encode '(variant { InitArg = record { greeting = "moin" } })' -d src/backend/backend.did -t '(Arg)'
Edit rust-analyzer.cargo.features
in your .vscode/settings.json
to enable the canbench
feature for the rust-analyzer
so canbench
blocks are analyzed.
Run canbench --persist
to persist the current benchmark results, then canbench
after code changes to compare the current benchmark results with the persisted ones.
There are some example integration tests leveraging pocket-ic
in src/backend/tests
. You can run them with cargo test
.
Simple automatic fuzz testing for Internet Computer Protocol (ICP) canisters, configuration is defined in cuzz.json
. Read more here.
The canisters candid file is generated by leveraging the ic_cdk::export_candid!()
macro and candid-extractor
. You can read more about this approach here.