Assuming that contractA is a NFT module and contractB depends on A’s state like whether a user has a specfic NFT.
How can I retrieve the state of A or call function from B?
Hi.
Contracts currently communicate via message passing. Thus for a contract B to read contract A’s state contract A must expose an entrypoint that allows it to do so.
For example our CIS-1 standard mandates that any NFT contract contains a method “balanceOf” which can be invoked by another contract to obtain information about how many tokens a particular address owns. See https://github.com/Concordium/concordium-rust-smart-contracts/blob/main/examples/cis1-single-nft/src/lib.rs#L338 for an example of such a contract.
In particular look at how that entrypoint responds ( https://github.com/Concordium/concordium-rust-smart-contracts/blob/main/examples/cis1-nft/src/lib.rs#L452 ) by sending a message to the address that it was given.
Thank you for providing an example. I’ve checked the source code.
So let me ask you an additional question about ContractBalanceOfQueryParams
.
I think there are two cases of using Balanceof, one is to call it directly and the other is to use it as an entrypoint.
For example, the former is when calling CIS1-NFT.balanceOf directly from the client, and the latter is when calling from ContractB.checkAuthority.
What are the parameters that should be applied to result_contract
and result_function
in each case?
There currently isn’t really a way to invoke the entrypoint “from the client”. “View functions” are coming in the next release of the node.
When invoking it from the contract, the result_contract
should be the address of the contract you wish to give the information to, and result_function should be its entrypoint (i.e., receive method).
One common example would be, if contract A wants to check the balance in contract B, that result_contract is the address of contract A.
I see, thanks for your quick reply. View functions
would be great!
And I think I understand. Maybe come back to ask again, but I’ll try to write the code.
hi, your explanation really helped! @abizjak
I have one more question.
In this case(https://github.com/Concordium/concordium-rust-smart-contracts/blob/main/examples/cis1-nft/src/lib.rs#L456), the parameter which is sent back to the caller function is BalanceOfQueryResponse
but that is different from the original parameter.
If the NFT module has to know the caller module’s schema, it’s not versatile.
Is there any good workaround?
The format of the response is specified in the CIS-1 standard, see CIS-1: Concordium Token Standard - Interoperability Specifications
So the parameter to the callback will always be serialized in the way specified in the standard, so anybody calling the contract can rely on this (and thus has to have an entrypoint that accepts the specified parameter). Does this make sense?
Thanks for sharing the link!
I guess I understand that. but if I’m right, I think it’s difficult to design an entrypoint of caller module.
Assuming that a caller module is ModuleA.someFunc()
which uses CIS1NFT.balanceOf()
internally and CIS1NFT.balanceOf()
send back a message to ModuleA.someFunc()
with BalanceOfQueryResponse
.
In that case, client also should call ModuleA.someFunc()
with BalanceOfQueryResponse
, right?
Or should I have a facade function between ModuleA.someFunc()
and CIS1NFT.balanceOf()
?
Good question.
What is the best solution would depend a bit on what ModuleA.someFunc does, but the way I would recommend to set this up is to have separate entrypoints.
ModuleA.someFunc
is what is invoked by the client
ModuleA.balanceResponseHandler
is the entrypoint you give to CIS1NFT, so this is where execution will resume after the balanceof query.
Note that this is just for entrypoints, and you could then still handle most of the logic in one place, e.g., in rust you’d have
fn someFunc(...) -> ReceiveResult<..> {
/// parse parameter and any immediate checks, then
worker(parsed parameters, ...)
}
fn balanceResponseHandler(...) -> ReceiveResult<...> {
/// parse parameter and any immediate checks (e.g. make sure that you are in the correct state to be invoked here
worker(parsed parameters, ...)
}
/// Non-trivial logic of the contract
fn worker(...) -> ... {
}
Does this make sense? In many cases it would make sense to have different workers as well.
Yes, that makes sense. Thank you for teaching me thoroughly!