Querying Contract State

You can view the state of a contract either as an external client using the CLI or while executing another contract. In this section, we will explore the two types of queries: raw and custom. We will also look at the semantics of querying via an external client and as an internal client (another contract). We will pay special attention not only to how it works in practice but also to the design and security issues of executing queries from one contract to another.

Raw queries

The simplest query to implement is just raw read access to the key-value store. If the caller (either an external client or another contract) passes in the raw binary key used in the contract's storage, we can easily return the raw binary value. The benefit of this approach is that it is easy to implement and universal. The downside is that it requires knowledge of the exact contract being executed, as it expects the caller to know the implementation of the contract storage.

As Raw Queries are implemented inside the wasmd runtime, they circumvent the VM. As a consequence, they require no support from the CosmWasm contract, and the whole contract state is visible. The query_raw function is exposed to all callers, both external and internal.

Custom queries

As it is often not desirable to tightly couple to implementation details, it is preferable to depend on an interface instead. For example, we can define a standard for "CW20" ExecuteMsg for calling the contract, and we can define such a standard for a QueryMsg as well. For example, querying balance by address, querying allowance via granter + grantee, and querying token info (ticker, decimals, etc). By defining a standard interface, we allow for many implementations, including complex contracts, where the "CW20" interface is only a small subset of their functionality.

To enable custom queries, we allow each contract to expose a query function that can access its data store in read-only mode. This allows loading any data and even performing calculations on it. This method is exposed as query_custom to all callers, both external and internal. As the data format (both for query and response) can be anything the contract desires, it should be documented in the public schema, along with ExecuteMsg and InitMsg.

Note that executing a contract may consume an unbounded amount of gas, whereas query_raw will read one key and has a small, mostly fixed cost. With query_custom, we need to enforce a gas limit. As the method to enforce a gas limit is done differently for external and internal calls, we will delve into the details below.

External queries

External queries are the primary way that web and CLI clients interact with the blockchain. They make a call to Tendermint RPC, which in turn calls abci_query in the Cosmos SDK and delegates the task to the appropriate module. To prevent potential attack vectors, such as using a wasm contract with an infinite loop to create a DoS attack on a public RPC node that exposes querying, a fixed gas limit needs to be defined for all query_custom transactions called externally. This limit is not used to charge a fee but is instead used to limit abuse.

However, it is challenging to define a standard value for the gas limit since a publicly exposed node would likely set a small gas limit, while an archive node performing complex queries would require a different value. To address this issue, a gas limit for all query_custom calls can be defined in an app-specific configuration file. Node operators can customize this value, while a sensible default limit is set to allow public nodes to protect themselves from complex queries. Optional queries can perform large aggregations over all contract data in specially-configured nodes.

It's important to note that the abci_query call never reads the current "in-progress" state of the modules. Instead, it uses a read-only snapshot of the state after the last committed block.

Internal queries

While many interactions between contracts can easily be modeled by sending messages, there are some cases where we would like to synchronously query other modules without altering their state. For example, if you wanted to resolve a name to an Address, or if you wanted to check the status of some account before enabling an action. Even though you could model this as a series of messages, that would be quite complex and create hassles when implementing simple use-cases.

Note that this design would also violate one of the basic principles of the Actor Model, as each contract should only have exclusive access to its own internal state. Both query_raw and query_custom fail in this regard. Far from just being a theoretical issue, this may lead to concurrency and reentrancy issues if not handled correctly. As there is no need to push such safety-critical reasoning into the laps of the contract developers, the security guarantees can be embedded in the framework itself.

As such, we provide the Querier with read-only access to the state snapshot right before the execution of the current CosmWasm message. Since we take a snapshot and both the executing contract and the queried contract have read-only access to the data before the contract execution, this is still safe due to Rust's borrowing rules. The current contract only writes to a cache, which is flushed afterward on success.

Keep in mind that all queries are performed as part of a transaction, which already has a strongly enforced gas limit. All storage reads and data processing performed as part of a query are deducted from the same gas meter as the rest of the transaction and thus limit processing time.

Last updated