How to access the state of another contract?

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?

1 Like

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.

2 Likes

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.

1 Like

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.

1 Like

Yes, that makes sense. Thank you for teaching me thoroughly!

1 Like