When defining an update method with rust, what is the difference between
- #[update],
- #[update(guard = “is_user”)],
and - #[update(name = “funtionName”)] ?
When defining an update method with rust, what is the difference between
#update
This marks the following fn as an update call and uses its name. If your next line is
fn my_awesome_update_func
you could call your canister with this name and it would perform the update.
#[update(guard = “is_user”)]
The guard parameter can be read as this, in layman’s terms: Before you run my function, go run is_user, and only run my function if the guard returned OK().
Here’s a guard example function:
fn is_controller() -> Result<(), String> {
RUNTIME_STATE.with(|state| {
if state
.borrow()
.data
.canister_settings
.controllers
.contains(&state.borrow().env.caller())
{
Ok(())
} else {
Err("You are not a controller".to_string())
}
})
}
And I use it like so:
#[update(name = "sendCycles", guard = "is_controller")]
async fn send_cycles() -> bool {
[...]
So my send_cycles fn gets “renamed” to sendCycles (as the js convention uses camelCase) and I also guard this function so it can be called only by the canister’s controllers.
I think I answered 2 & 3 with one example.
Also, while we’re talking about this, it’s worth mentioning the following macro:
#[candid_method(update, rename = "stop_cron")]
By annotating functions with this macro, we can then use the self rust → candid interface thingy, like so:
// Auto export the candid interface
candid::export_service!();
#[query(name = "__get_candid_interface_tmp_hack")]
#[candid_method(query, rename = "__get_candid_interface_tmp_hack")]
fn __export_did_tmp_() -> String {
__export_service()
}
This will take every function annotated with #[candid_method()] and add it to the candid output. Works like a charm for fast prototyping, and can be later modified to act as a test for your “manually curated” did file.
This is an awesome solution to my question!
Would it be possible to read the canister code where you define the static variable RUNTIME_STATE and contain more complete examples of how to use the “#[candid_method” macro?
Sure, I just pushed a “quickstart” project that I use to test things out. Check it out here
@GLdev Hey, I cannot make this, guard = "is_controller")]
you mentioned work, and I cannot find any use of it in your repo as well, could you give more details about it please ?
For that to work you’ll need to first get your canister’s controllers, have them in a vec or something and then iterate over that vec to check if the caller is on that list.
There are a few ways to get a canister’s controllers, but surprisingly not an ic_cdk simple API to do so. The easiest is to first add the return from “ic_cdk::api::caller()” inside your init() function. That will give you the dfx principal (with the latest dfx deploy). With that added you could have a manual function, guarded by the first controller, to add more controllers.
An advanced method is to call the management canister asking for a status of your own canister_id, and that will return ALL the controllers of a canister.
Check my post here. Also you can check my quickstart scaling repo for the implementation of the above is_controller. Note that how you add & keep track of controllers is up to you, until we get a simple ic_cdk api.