Using dynamic calls
If you find yourself needing more expressive power than the references have to offer, you can use the dynamically constructed calls.
Even though the dynamically constructed calls are the more versatile and powerful method, we encourage you to stick to references if your use case allows it. The drawbacks here are the complete lack of the compiler's assistance when constructing calls: the errors will present themselves only at runtime.
There is only one step involved in the whole process, albeit a little more cumbersome.
Building and executing the call
First of all, you will need some imports:
We will paste the whole code snippet and later explain the less obvious parts:
If you come from the OOP world, this style is similar to the 'fluent interface' pattern.
We initialize our builder with the
build_call
method.With
call
, we specify what contract account we want (this differs from the references, which were initialized with a code hash. Here we need the contract's account, which you can easily find in the Contracts UI).With
exec_input
we specify which method to call (by using a selector: more on that later) and usingpush_arg
to supply the arguments.By using
transferred_value
we can send some tokens to the receiving method. Note that this method needs to be marked with thepayable
macro in order to be able to act on the transfer in any way.We need to specify the return type of the call using
returns
. Note that each call will have the original return type of the method wrapped in aResult<T, ink::LangError>
, on account of all Ink! messages wrapping their return types this way.To actually fire the call we will use the
invoke
method.
As with references, make sure to inspect the result of the call, handling all the failure cases.
Selectors
You've probably noticed that we didn't use the called method's name in the call above. Instead, we needed to use a selector, which is a number associated with each message in Ink!. There are two ways of going about the selectors: the explicit one, presented above, and the macro-based implicit method.
Explicit selectors
In order to use explicit selectors, you first need to declare them on your 'callee' contract. As you're probably able to guess by now, this is done using a macro:
It is a good practice to create constants for each selector to help eliminate errors. The selectors are actually four-element byte arrays, so we declare it in the following way:
Of course, to allow other modules to use it, we need to export our constant:
Just as with our reference export, this needs to be placed at the top level.
Now we're able to import it inside our Bulletin Board contract and use it for the ExecutionInput
. as in the call snippet above.
Macro-based selectors
In case you don't feel like specifying the selectors manually, you can use the ink::selector_bytes!
macro (notice the exclamation mark at the end of the name).
If we use it in our example from before, the Selector
instantiation will change to the following:
Which way you end up using is entirely up to you!
Closing remarks
You are now ready to leverage the full potential of Ink! smart contracts and write expressive, complex code. In case you'd like to dive deeper into the cross-contract calls, we encourage you to take a look at the official Ink! documentation.
As the last remark, please remember to always test your contracts on the Testnet first, taking into account different edge cases (including high load and potentially malicious actors interacting with your contract). We are huge advocates of testing even the simplest of contracts but if you're making cross-contract calls, the need for thorough testing is even more apparent.
Last updated