This site requires Javascript to be enabled.

Contract semantics

This document clarifies how a CosmWasm contract interacts with its environment, focusing on the two main types of actions: mutating actions, which receive DepsMut and can modify the state of the blockchain, and query actions, which run on a single node with read-only access to the data.

Execution

This section covers how the execute call operates, but it's important to note that the same principles apply to other mutating actions, such as instantiate, migrate, sudo, and so on.

SDK context

Before diving into CosmWasm, it's essential to understand the semantics enforced by the blockchain framework it integrates with—the Cosmos SDK. This framework operates on the Tendermint BFT Consensus Engine. Let's first examine how transactions are processed before and after they interact with CosmWasm.

  1. Transaction Selection: The Tendermint engine seeks 2/3+ consensus on a list of transactions to include in the next block. These transactions undergo a minimal pre-filter by the Cosmos SDK module to ensure they are validly formatted, have sufficient gas fees, and are signed by an account with enough funds to pay the fees. It's worth noting that transactions that result in an error may still be included in a block.
  2. Transaction Execution: Once a block is committed (typically every 5 seconds or so), the transactions are sequentially fed to the Cosmos SDK for execution. Each transaction returns a result or an error, along with event logs, which are recorded in the TxResults section of the next block. The AppHash (or Merkle proof of blockchain state) generated after executing the block is included in the next block.
  3. Isolated Context: The Cosmos SDK's BaseApp handles each transaction in an isolated context. It verifies all signatures, deducts the gas fees, and sets the "Gas Meter" to limit execution to the amount of gas paid for by the fees. It creates an isolated context to run the transaction, allowing the code to read the current state of the chain but only write to a cache that may be committed or rolled back if an error occurs.
  4. Atomic Transactions: A transaction may consist of multiple messages, each executed in turn under the same context and gas limit. If all messages succeed, the context is committed to the underlying blockchain state, and the results of all messages are stored in the TxResult. If any message fails, subsequent messages are skipped, and all state changes are reverted, ensuring atomicity.

The x/wasm is a custom Cosmos SDK module that processes specific messages for uploading, instantiating, and executing smart contracts. It accepts a properly signed MsgExecuteContract and routes it to Keeper.Execute, which loads the appropriate smart contract and calls execute on it. This method may return either success (with data and events) or an error. In the case of an error, the entire transaction in the block is reverted.

Basic execution

When implementing a contract, you provide the following entry point:

pub fn execute(    deps: DepsMut,    env: Env,    info: MessageInfo,    msg: ExecuteMsg,) -> Result<Response, ContractError> { }

With DepsMut, this function can read and write to the backing Storage, use the Api to validate addresses, and Query the state of other contracts or native modules. Once done, it returns either Ok(Response) or Err(ContractError). Here's what happens next:

  • Error Handling: If it returns Err, the error is converted into a string representation (err.to_string()) and returned to the SDK module. All state changes are reverted, and x/wasm returns this error message, generally aborting the transaction and returning the same error message to the external caller.
  • Successful Execution: If it returns Ok, the Response object is parsed and processed. The Response struct includes submessages, messages, attributes, and optional data.
pub struct Response<T = Empty>where    T: Clone + fmt::Debug + PartialEq + JsonSchema,{    pub submessages: Vec<SubMsg<T>>,    pub messages: Vec<CosmosMsg<T>>,    pub attributes: Vec<Attribute>,    pub data: Option<Binary>,}

In the Cosmos SDK, a transaction returns a series of events to the user, along with an optional "data result." The ResultHash includes the Code (indicating success or error) and Result (data) from the transaction. Events and logs are accessible through queries, though they don't have light-client proofs.

Dispatching messages

Contracts often need to interact with external entities, such as moving tokens or calling other contracts. This is where messages come in. A contract can return multiple CosmosMsg objects, each representing an external call. These messages are executed in a depth-first order, ensuring that all messages and submessages are processed correctly.

CosmWasm prevents reentrancy attacks by following the actor model, which avoids nested function calls and instead returns messages that are executed later. This ensures that all state carried between calls is stored in persistent storage rather than memory, reducing the risk of vulnerabilities.

Submessages

As of CosmWasm 0.14, submessages were introduced to allow contracts to receive the results of dispatched messages. This feature is useful for cases like:

  • Obtaining the contract address when instantiating a new contract
  • Handling errors from cross-contract calls
  • Limiting gas usage for specific submessages

A submessage wraps a CosmosMsg inside a SubMsg struct, which includes additional parameters such as a unique ID, gas limit, and reply strategy.

pub struct SubMsg<T = Empty>where    T: Clone + fmt::Debug + PartialEq + JsonSchema,{    pub id: u64,    pub msg: CosmosMsg<T>,    pub gas_limit: Option<u64>,    pub reply_on: ReplyOn,}pub enum ReplyOn {    Always,    Error,    Success,    Never,}

Submessages allow for partial state commits and error handling without rolling back the entire transaction. The calling contract can handle the result of the submessage using a reply entry point, which receives the result and any associated events.

Handling replies

To handle the reply from a submessage, the calling contract must implement a reply entry point:

#[entry_point]pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result<Response, ContractError> { }pub struct Reply {    pub id: u64,    pub result: ContractResult<SubcallResponse>,}pub struct SubcallResponse {    pub events: Vec<Event>,    pub data: Option<Binary>,}

After a submessage is executed, the caller can process the result based on the original ID and the execution result. The reply call can return a Response object, which may include additional messages, submessages, and data.

Order and rollback

Submessages follow the same depth-first order as messages, with replies treated as immediate additional message calls. The execution order and rollback behavior ensure that contracts can handle complex interactions securely and predictably.

Query semantics

In contrast to mutating actions, query actions provide read-only access to the contract's environment. Queries are essential for retrieving information, such as a contract's balance or state, during execution.

CosmWasm enables synchronous queries through a read-only Querier interface. These queries are serialized into a QueryRequest struct and passed to the runtime, where they are interpreted by the x/wasm SDK module.

pub enum QueryRequest<C: CustomQuery> {    Bank(BankQuery),    Custom(C),    Staking(StakingQuery),    Stargate {        path: String,        data: Binary,    },    Ibc(IbcQuery),    Wasm(WasmQuery),}

The QuerierWrapper simplifies the process of generating and using queries, providing convenience methods that wrap QueryRequest and Querier.raw_query.

For a more detailed explanation of the Querier design, see Querying Contract State.