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
15 changes: 13 additions & 2 deletions rust/inter-canister-calls/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ all: test
.SILENT: deploy
build:
dfx deploy

SHELL := /bin/bash
.SHELLFLAGS := -euo pipefail -c
.PHONY: test
.SILENT: test
test: build
Expand All @@ -18,7 +19,17 @@ test: build
dfx canister call caller call_get "( principal \"`dfx canister id counter`\" )" | grep '(variant { Ok = 8 : nat })' && echo 'PASS'
dfx canister call caller stubborn_set "( principal \"`dfx canister id counter`\", 42 : nat )" | grep '(variant { Ok })' && echo 'PASS'
dfx canister call caller call_get "( principal \"`dfx canister id counter`\" )" | grep '(variant { Ok = 42 : nat })' && echo 'PASS'
dfx canister call caller sign_message '("Some text to be signed")' | grep "Ok = \"" && echo PASS
# We'll test that the counter's balance increases when calling send_cycles to it.
# Storing the original balance in a file is the easiest with make.
echo $$(dfx canister status counter | awk '/Balance/ { gsub(/_/, "", $$2); print $$2 }') > .counter_before
# Send 10% of the caller's available cycles
CYCLES_TO_SEND=$$(dfx canister status caller | awk '/Balance/ { gsub(/_/, "", $$2); printf("%d", int($$2/10)) }'); \
echo "Sending $$CYCLES_TO_SEND cycles to counter canister"; \
dfx canister call caller send_cycles "( principal \"`dfx canister id counter`\", $$CYCLES_TO_SEND: nat64 )" | grep '(variant { Ok })' && echo PASS
# Check that the counter's balance increased
AFTER=$$(dfx canister status counter | awk '/Balance/ { gsub(/_/, "", $$2); print $$2 }'); \
BEFORE=$$(cat .counter_before); \
test $$AFTER -gt $$BEFORE && echo "PASS (cycles increased: $$BEFORE -> $$AFTER)"

.PHONY: clean
.SILENT: clean
Expand Down
6 changes: 3 additions & 3 deletions rust/inter-canister-calls/src/caller/caller.did
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ type GetResult = variant {
"Err" : text;
};

type SignMessageResult = variant {
"Ok" : text;
type SendCyclesResult = variant {
"Ok" : null;
"Err" : text;
};

Expand All @@ -24,5 +24,5 @@ service : {
"call_get": (counter: principal) -> (GetResult);
"call_increment": (counter: principal) -> (IncrementResult);
"stubborn_set": (counter: principal, new_value: nat) -> (StubbornSetResult);
"sign_message": (text) -> (SignMessageResult);
"send_cycles": (target: principal, amount: nat64) -> (SendCyclesResult);
}
64 changes: 15 additions & 49 deletions rust/inter-canister-calls/src/caller/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// Some of the imports will only be used in later examples; we list them here for simplicity
use candid::{Nat, Principal};
use ic_cdk::api::time;
use ic_cdk::call::{Call, CallErrorExt, RejectCode};
use ic_cdk::management_canister::{EcdsaCurve, EcdsaKeyId, SignWithEcdsaArgs, SignWithEcdsaResult, cost_sign_with_ecdsa};
use ic_cdk::call::{Call, CallErrorExt};
use ic_cdk::management_canister::{DepositCyclesArgs, CanisterId};
use ic_cdk_macros::update;
use sha2::{Digest, Sha256};

// When calling other canisters:
// 1. The simplest is to mark your function as `update`. Then you can always call any public
// endpoint on any other canister.
// 2. Mark the function as `async`. Then you can use the `Call` API to call other canisters.
//
// 1. The simplest is to mark your function as `update`. Then you can always call any public
// endpoint on any other canister.
// 2. Mark the function as `async`. Then you can use the `Call` API to call other canisters.
//
// This particular example requires the caller to provide the principal (i.e., ID) of the counter canister.
#[update]
pub async fn call_get_and_set(counter: Principal, new_value: Nat) -> Nat {
Expand Down Expand Up @@ -136,52 +137,17 @@ pub async fn stubborn_set(counter: Principal, new_value: Nat) -> Result<(), Stri
}

#[update]
pub async fn sign_message(message: String) -> Result<String, String> {
let message_hash = Sha256::digest(&message).to_vec();

let request = SignWithEcdsaArgs {
message_hash,
// This example does not use the fancier signing features
derivation_path: vec![],
key_id: EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
// This is the key name used for local testing; different
// key names are needed for the mainnet
name: "dfx_test_key".to_string(),
},
pub async fn send_cycles(target: CanisterId, amount: u64) -> Result<(), String> {
let request = DepositCyclesArgs {
canister_id: target,
};

let cycles_cost = cost_sign_with_ecdsa(&request).map_err(|e| {
format!(
"Failed to compute cycles cost for signing with ECDSA: {:?}",
e
)
})?;

// Use bounded-wait calls in this example, since the amount attached is
// fairly low, and losing the attached cycles isn't catastrophic.
match Call::bounded_wait(Principal::management_canister(), "sign_with_ecdsa")
match Call::bounded_wait(Principal::management_canister(), "deposit_cycles")
.with_arg(&request)
// Signing with a test key requires 30 billion cycles
.with_cycles(cycles_cost)
.with_cycles(amount as u128)
.await
{
Ok(resp) => match resp.candid::<SignWithEcdsaResult>() {
Ok(signature) => Ok(hex::encode(signature.signature)),
Err(e) => Err(format!("Error decoding response: {:?}", e)),
},
// A SysUnknown reject code only occurs due to a bounded-wait call timing out.
// It means that no cycles will be refunded, even
// if the call didn't make it to the callee. Here, this is fine since
// only a small amount is used.
Err(ic_cdk::call::CallFailed::CallRejected(e))
if e.reject_code() == Ok(RejectCode::SysUnknown) =>
{
Err(format!(
"Got a SysUnknown error while signing message: {:?}; cycles are not refunded",
e
))
}
Err(e) => Err(format!("Error signing message: {:?}", e)),
Ok(_) => Ok(()),
Err(e) => Err(format!("Error attaching {} cycles: {:?}", amount, e)),
}
}
}