Skip to content

Commit

Permalink
Move wasmvm/spec and cosmwasm/SEMANTICS.md here
Browse files Browse the repository at this point in the history
  • Loading branch information
kulikthebird committed Nov 18, 2024
1 parent 56c3bfb commit 45a6837
Showing 1 changed file with 274 additions and 0 deletions.
274 changes: 274 additions & 0 deletions src/pages/core/semtantics.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
---
tags: ["core"]
---

# Contract Semantics

This document aims to clarify the semantics of how a CosmWasm contract interacts with its
environment. There are two main types of actions: _mutating_ actions, which are able to modify the
state of the blockchain, and _query_ actions, which are run on a single node with read-only access
to the data.

## Definitions

**Contract** is as some wasm code uploaded to the system, initialized at the creation of the
contract. This has no state except that which is contained in the wasm code (eg. static constants)

**Instance** is one instantiation of the contract. This contains a reference to the contract, as
well as some "local" state to this instance, initialized at the creation of the instance. This state
is stored in the kvstore, meaning a reference to the code plus a reference to the (prefixed) data
store uniquely defines the smart contract.

Example: we could upload a generic "ERC20 mintable" contract, and many people could create
independent instances of the same bytecode, where the local data defines the token name, the issuer,
the max issuance, etc.

- First you **create** a _contract_
- Then you **instantiate** an _instance_
- Finally users **invoke** the _instance_

## Execution

In the section below, we will discuss how the `execute` call works, but the same semantics apply to
any other _mutating_ action - `instantiate`, `migrate`, `sudo`, etc.

### SDK Context

Before looking at CosmWasm, we should look at the semantics enforced by the blockchain framework we
integrate with - the [Cosmos SDK](https://v1.cosmos.network/sdk). It is based upon the
[Tendermint BFT](https://tendermint.com/core/) Consensus Engine. Let us first look how they process
transactions before they arrive in CosmWasm.

First, the Tendermint engine will seek 2/3+ consensus on a list of transactions to be included in
the next block. This is done _without executing them_. They are simply subjected to a minimal
pre-filter by the Cosmos SDK module, to ensure they are validly formatted transactions, with
sufficient gas fees, and signed by an account with sufficient fees to pay it. Notably, this means
many transactions that error may be included in a block.

Once a block is committed, the transactions are then fed to the Cosmos SDK sequentially in order to
execute them. Each one returns a result or error along with event logs, which are recorded in the
`TxResults` section of the next block. The `AppHash` (or merkle proof or blockchain state) after
executing the block is also included in the next block.

The Cosmos SDK `BaseApp` handles each transaction in an isolated context. It first verifies all
signatures and deducts the gas fees. It sets the "Gas Meter" to limit the execution to the amount of
gas paid for by the fees. Then it makes an isolated context to run the transaction. This allows the
code to read the current state of the chain (after the last transaction finished), but it only
writes to a cache, which may be committed or rolled back on error.

A transaction may consist of multiple messages and each one is executed in turn under the same
context and same gas limit. If all messages succeed, the context will be committed to the underlying
blockchain state and the results of all messages will be stored in the `TxResult`. If one message
fails, all later messages are skipped and all state changes are reverted. This is very important for
atomicity. That means Alice and Bob can both sign a transaction with 2 messages: Alice pays Bob 1000
ATOM, Bob pays Alice 50 ETH, and if Bob doesn't have the funds in his account, Alice's payment will
also be reverted. This is just like a DB Transaction typically works.

[`x/wasm`](https://github.com/CosmWasm/wasmd/tree/master/x/wasm) is a custom Cosmos SDK module,
which processes certain messages and uses them to upload, instantiate, and execute smart contracts.
In particular, it accepts a properly signed
[`MsgExecuteContract`](https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1/tx.proto),
routes it to
[`Keeper.Execute`](https://github.com/CosmWasm/wasmd/blob/master/x/wasm/keeper/keeper.go), which
loads the proper smart contract and calls `execute` on it. Note that this method may either return a
success (with data and events) or an error. In the case of an error here, it will revert the entire
transaction in the block. This is the context we find ourselves in when our contract receives the
`execute` call.

### Basic Execution

When we implement a contract, we provide the following entry point:

```rust template="core"
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
// [...]
}
```

With [`DepsMut`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.DepsMut.html), this can
read and write to the backing
[`Storage`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html), as well as use the
[`Api`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html) to validate addresses, and
use [`QuerierWrapper`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.QuerierWrapper.html)
the state of other contracts or native modules. Once it is done, it returns either `Ok(Response)` or
`Err(ContractError)`.

If it returns `Err`, this error is converted to a string representation, and it's returned to the
SDK module. _All state changes are reverted_ and `x/wasm` returns this error message, which will
_generally_ abort the transaction, and return the error message to the external caller.

If it returns `Ok`, the
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) object is parsed
and processed.

In the Cosmos SDK, a transaction returns a number of events to the user, along with an optional data
"result". This result is hashed into the next block hash to be provable and can return some
essential state (although in general client apps rely on Events more). This result is more commonly
used to pass results between contracts or modules in the sdk.

### Dispatching Submessages

Now let's move onto the `messages` field of the
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html). Some contracts
are fine only talking with themselves. But many want to move tokens or call into other contracts for
more complex actions. This is where messages come in. We return
[`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html), which is a
serializable representation of any external call the contract can make.

This may be hard to understand at first. "Why can't I just call another contract?", you may ask.
However, we do this to prevent one of most widespread and hardest to detect security holes in
Ethereum contracts - reentrancy. We do this by following the actor model, which doesn't nest
function calls, but returns messages that will be executed later. This means all state that is
carried over between one call and the next happens in storage and not in memory. For more
information on this design, I recommend you read
[our docs on the Actor Model](architecture/actor-model.mdx).

A common request was the ability to get the result from one of the messages you dispatched. For
example, you want to create a new contract with
[`WasmMsg::Instantiate`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.WasmMsg.html#variant.Instantiate),
but then you need to store the address of the newly created contract in the caller. this is possible
with `messages`. It also solves a similar use-case of capturing the error results, so if you execute
a message from e.g. a cron contract, it can store the error message and mark the message as run,
rather than aborting the whole transaction. It also allows for limiting the gas usage of the
submessage (this is not intended to be used for most cases, but is needed for eg. the cron job to
protect it from an infinite loop in the submessage burning all gas and aborting the transaction).

This makes use of
[`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html) as above, but it
wraps it inside a [`SubMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.SubMsg.html)
envelope.

What are the semantics of a submessage execution. First, we create a sub-transaction context around
the state, allowing it to read the latest state written by the caller, but write to yet-another
cache. If `gas_limit` is set, it is sandboxed to how much gas it can use until it aborts with
`OutOfGasError`. This error is caught and returned to the caller like any other error returned from
contract execution (unless it burned the entire gas limit of the transaction). What is more
interesting is what happens on completion.

If it return success, the temporary state is committed (into the caller's cache), and the
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) is processed as
normal. Once the response is fully processed, this may then be intercepted by the calling contract
(for `ReplyOn::Always` and `ReplyOn::Success`). On an error, the subcall will revert any partial
state changes due to this message, but not revert any state changes in the calling contract. The
error may then be intercepted by the calling contract (for `ReplyOn::Always` and `ReplyOn::Error`).
_In this case, the messages error doesn't abort the whole transaction_

Note, that error doesn't abort the whole transaction _if and only if_ the `reply` is called - so in
case of `ReplyOn::Always` and `ReplyOn::Error`. If the submessage is called with `ReplyOn::Success`
(or `ReplyOn::Never`, which makes it effectively a normal message), the error in subsequent call
would result in failing whole transaction and not commit the changes for it. The rule here is as
follows: if for any reason you want your message handling to succeed on submessage failure, you
always have to reply on failure.

Obviously - on the successful processing of sub-message, if the reply is not called (in particular
`ReplyOn::Error`), the whole transaction is assumed to succeed, and is committed.

#### Handling the Reply

In order to make use of `messages`, the calling contract must have an extra entry point:

```rust filename="contract.rs" template="core"
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> StdResult<Response> {
// [...]
}
```

After the `message` is finished, the caller will get a chance to handle the result. It will get the
original `id` of the subcall so it can switch on how to process this, and the `Result` of the
execution, both success and error. Note that it includes all events returned by the submessage,
which applies to native sdk modules (like Bank) as well as the data returned from below. This and
the original call id provide all context to continue processing it. If you need more state, you must
save some local context to the store (under the `id`) before returning the `message` in the original
`execute`, and load it in `reply`. We explicitly prohibit passing information in contract memory, as
that is the key vector for reentrancy attacks, which are a large security surface area in Ethereum.

The `reply` call may return `Err` itself, in which case it is treated like the caller errored, and
aborting the transaction. However, on successful processing, `reply` may return a normal
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html), which will be
processed as normal - events added to the EventManager, and all `messages` dispatched as described
above.

## TODO tkulik: How `msg_responses` is handled during execution

The responses emitted by the submessage are gathered in the
[`message_responses`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.SubMsgResponse.html#structfield.msg_responses)
field of the
[SubMsgResponse](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.SubMsgResponse.html)
structure. **Wasmd** allows chains to translate a single contract message into multiple SDK
messages. In that case all the MsgResponses from each are concatenated into this flattened `Vec`.

## TODO tkulik: Make sure how data is handled in multiple submessage scenario. Related to first PR.

The one _critical difference_ with `reply`, is that we _do not drop data_. If `reply` returns
`data: Some(value)` in the
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) object, we will
overwrite the `data` field returned by the caller. That is, if `execute` returns
`data: Some(b"first thought")` and the `reply` (with all the extra information it is privy to)
returns `data: Some(b"better idea")`, then this will be returned to the caller of `execute` (either
the client or another transaction), just as if the original `execute` and returned
`data: Some(b"better idea")`. If `reply` returns `data: None`, it will not modify any previously set
data state. If there are multiple messages all setting this, only the last one is used (they all
overwrite any previous `data` value). As a consequence, you can use `data: Some(b"")` to clear
previously set data. This will be represented as a JSON string instead of `null` and handled as any
other `Some` value.

#### Order and Rollback

## TODO tkulik: Make sure that the order is properly described here

Submessages follow a _depth first_ order rules, with their replies considered as an immediate
additional message call. Here is a simple example. Contract **A** returns messages **S1** and
**S2**, and message **M1**. Submessage **S1** returns submessage **N1**. The order will be: **S1,
N1, reply(S1), S2, reply(S2), M1**.

Please keep in mind that submessage `execution` and `reply` can happen within the context of another
submessage. For example `contract-A--submessage --> contract-B--submessage --> contract-C`. Then
`contract-B` can revert the state for `contract-C` and itself by returning `Err` in the submessage
`reply`, but not revert contract-A or the entire transaction. It just ends up returning `Err` to
contract-A's `reply` function.

Note that errors are not handled with `ReplyOn::Success`, meaning, in such a case, an error will be
treated just like a normal `message` returning an error. This diagram may help explain. Imagine a
contract returned two submesssages - (a) with `ReplyOn::Success` and (b) with `ReplyOn::Error`:

| processing a) | processing b) | reply called | may overwrite result from reply | note |
| ------------- | ------------- | ------------ | ------------------------------- | ------------------------------------------------- |
| ok | ok | a) | a) | returns success |
| err | err | none | none | returns error (abort parent transaction) |
| err | ok | none | none | returns error (abort parent transaction) |
| ok | err | a)b) | a)b) | if both a) and b) overwrite, only b) will be used |

## Query Semantics

Until now, we have focused on the
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) object, which
allows us to execute code in other contracts via the actor model. That is, each contract is run
sequentially, one after another, and no nested calls are possible. This is essential to avoid
reentrancy, which is when calling into another contract can change my state while I am in the middle
of a transaction.

However, there are many times we need access to information from other contracts in the middle of
processing, such as determining the contract's bank balance before sending funds. To enable this, we
have exposed the _read only_
[`QuerierWrapper`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.QuerierWrapper.html) to
enable _synchronous_ calls in the middle of the execution. By making it read-only (and enforcing
that in the VM level), we can prevent the possibility of reentrancy, as the query cannot modify any
state or execute our contract.

When we "make a query", we serialize a
[`QueryRequest` struct](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.QueryRequest.html)
that represents all possible calls, and then pass that over FFI to the runtime, where it is
interpreted in the `x/wasm` SDK module. This is extensible with blockchain-specific custom queries
just like [`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html)
accepts custom results.

While this is flexible and needed encoding for the cross-language representation, this is a bit of
mouthful to generate and use when I just want to find my bank balance. To help that, we often use
[`QuerierWrapper`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.QuerierWrapper.html).

0 comments on commit 45a6837

Please sign in to comment.