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 may have noticed that we didn’t use the method’s name directly in the call above. Instead, we used a selector, which is a number associated with each message in Ink!. There are two ways to handle selectors: the explicit method, as shown 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 a final note, always test your contracts on the Testnet first, considering various edge cases such as high load and interactions from potentially malicious actors. While we advocate testing even the simplest contracts, thorough testing is especially crucial when dealing with cross-contract calls.
Last updated