CallData like syntax for Motoko -or- Access to system level calls?

In solidity I can create a ‘catch-all’ function for my contracts that allow me call any contract that maybe created in the future(even if I don’t know the structure) by using something like:

someAddress.call({value: 1 **ether** })(abi.encodeWithSignature("someFunction(uint256)", _arg1))

The results come back as raw data and you can’t really do much with it except relay it back to the user as a big chunk of raw data…but you can still call it.

This really helps if you want to create something like a wallet that might need to call some as of yet unknown function on contracts in the future. Say you add a token to the wallet and that token has some non ERC20 function on it like claimReward() and since the wallet holds the token the call has to come from the wallet(as msg.caller) instead of the wallet owner.

Is there something similar to this in motoko? I’d imagine that in the rust SDK you can do something like this because you have to do everything via system calls. Does motoko expose the system calls in some way that we can access?

The scenario is, I want a motoko canister that has a function where I can pass in the args of a canister ID, the function to call, the signature of the function, that data to pass to the function. The canister function will construct the call and pass in the actor and that actor will see that the message(msg.caller) came from the canister instead of the identity.

Something like:

let returnData : Blob = await actor.fromPrincipal('targetCanister)._call( functionName: functionSig, encodedData: Blob);

1 Like

It is possible in rust now (ic-cdk has a call_raw function) but is not possible in Motoko.

Motoko is kinda limited right now.

1 Like

I guess I could write that in rust and reference the class from my motoko actor?

You could write a canister with call_raw in rust and then call this canister from your motoko canister, yes. This is slower, since your first call should also pass the consensus, but this will work for now.

1 Like

Hmmmm….that defeats the purpose of being called from the owning canister. The principal would be different. Can you reference rust code from motoko? Motoko from rust?

Yes, you can do both.
There are two ways of achieving this.
1 - when your canisters are in the same project:
how to reference motoko from rust - Basic dependency :: Internet Computer
how to reference rust from motoko - Modules and imports :: Internet Computer

2 - when your canisters are different projects, but you can provide an additional .did file:
I didn’t tried it yet, but it looks like even if these canisters are in different projects, you should still be able to provide .did files for them and make them reference each other using tutorials I’ve linked above.
In motoko you could also describe the referenced actor within the language itself like

type SomeActor = actor {
  inc  : shared () -> async ();
  read : shared () -> async Nat;
  bump : shared () -> async Nat;
}

then provide a principal for it.

These are both compile time references - the compiler will help you as much as it can, but your .did files should be correct.
If you want to runtime reference different canisters, there is only a solution for rust and we talked about it earlier in this thread.

2 Likes

My concern with most of this is that it requires me to pass control to the other canister which will have a different principle. I want a motoko wallet that can call bespoke functions. Looks like I have to do that in rust right now.

Who at DFINITY is currently overseeing the next version of motoko? It would be great to have a conversation about why the system calls are/are not included and if we could at least get call raw and query raw added.

Just for self-education: what the attack vector you can imagine with this approach?
I believe many ethereum-originated attacks will also play here on IC, like reentrancy, but nobody touched this topic here properly (at least before).

Hmm…I’m not too worried about reentrancy. You could wrap the function with “if principal != owner then throw” so that only the owner of the wallet could call it. I guess if that owner were a canister you might want to put some additional logic in their as well.

The goal of this functionality is to future proof the wallet for new functionality. This is mitigated by the upgrade functionality that the IC has, but I can also imagine a world where you want a canister(wallet) that can communicate with a wide array of services without having to know about all the exact api of all those services. With enough services the code would just get too big.

I guess you’d maybe also want to preclude the function from calling itself? It would be a good set of things to lay out and I’d imagine that ethereum would have a lot to contribute to the conversation.