Rust Create Canister / Inter-Canister Calls

Does anyone know how to use the Rust CDK call function?

I’m trying to call the management canister to create a new canister from wasm. It looks like I just need to implement the ArgumentEncoder trait on my struct, but it feels a bit laborious to do that for all my types.

ArgumentEncoder is implemented for all tuple types (T1, T2, ...), where Ti: CandidType. If you have a custom struct, you only need to #[derive(CandidType, Deserialize)], then pass in args as a tuple, for example (your_struct, 42, "Text").

2 Likes

Hey @chenyan - I’m having issues with ArgumentDecoder now. If its not too much hassle, could share an example of just calling a canister, and getting the result?

let (first_result, second_result) : (first_result_type, second_result_type) = 
  api::call::call(canister_id, "method", (first_arg, second_arg)).await?;

Both argument and return types are tuples. The type annotation on return type is required, as it cannot be inferred by Rust.

Thanks, let me give that a shot! Just so I make sure I understand

(first_result_type, second_result_type) (candid::Nat, candid::Principle)

That’s the return type of your method. If you are not sure, can you post the did file for that method?

1 Like

@chenyan - sorry for the confusion here is a full example

let canister : (candid::Principal) = ic_cdk::api::call(candid::Principal::management_canister(), "create_canister", ()).await?;

Yields the following error:

the trait `for<'a> ArgumentDecoder<'a>` is not implemented for `ic_types::Principal`

The return type is a tuple, even if it has only one value, so this will work:
let (canister,) : (candid::Principal,)

1 Like

:man_facepalming: DOH! Yes its working now. Thank you!!

Hey there!

I want to call canister methods without knowing their interface at compile time and I have a code like this

fn do_call(idl_args: IDLArgs, canister_id: Principal, method: &str) {
  let bytes = idl_args.to_bytes()?;

  let result = call_raw(
    canister_id,
    method,
    bytes,
    0,
  )
    .await;
}

It successfully executes inside a canister but the actual call is not performed. What am I doing wrong?

UPD: canister_id, method and idl_args are 100% fit in the remote interface.

@chenyan Please help me :frowning:

call_raw returns Future<Result<...>>, so you will need .await?.

Yes, it’s there. I did not include it into the snippet, sorry. There is also a check if result is Err and it is not.

I don’t understand the question here. What do you mean by “successfully executes, but the actual call is not performed”? Are you getting the return blob from call_raw?

The canister I’m calling to is logging every request it receives and my example request should also change its state (it is an update call).

When the second canister executes a code from the snippet, the first canister does not log a request and does not change its state as well. The response the second canister receives from that call is not Err, so it looks like everything should be all right, but it’s not.

I was thinking, am I encoding arguments correctly?
Or another possible issue: maybe the response is not Err but it returns an encoded error in the Ok variant?

Sorry, I just don’t know how to demonstrate that issue, since there is no error code or something.

Thank you for your patience.

You can use didc decode to inspect the return blob. Or if you know how many values the function returns, you can decode in Rust via Decode!(&result, IDLValue, IDLValue, ...)?

call_raw looks good to me, I think the problem might be on the other canister, where it doesn’t actually log every requests. So looking at the return result might help.

2 Likes

Thanks for the help!

How do you spawn a new canister in rust?

Here is my function in Motoko:

public query func test(number: Nat): async Nat {
        return number;
    };

Here is my function in Rust:

async fn test(number: i32) -> Result<i32, sudograph::async_graphql::Error> {
    let call_result: Result<(i32,), _> = ic_cdk::api::call::call(ic_cdk::export::Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(), "customGet", (number)).await;
    return Ok(call_result.unwrap().0);
}

I keep getting:

error[E0277]: the trait bound `i32: ArgumentEncoder` is not satisfied
   --> canisters/graphql/src/graphql.rs:24:157
    |
24  | ...aaba-cai").unwrap(), "customGet", (number)).await;
    |                                      ^^^^^^^^ the trait `ArgumentEncoder` is not implemented for `i32`
    | 
   ::: /home/lastmjs/.cargo/registry/src/github.com-1ecc6299db9ec823/ic-cdk-0.3.0/src/api/call.rs:260:22
    |
260 | pub async fn call<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
    |                      --------------- required by this bound in `sudograph::ic_cdk::call`

If I try just text or numbers as arguments to the call, nothing works. Is there some import I am missing?

1 Like