diff --git a/SEMANTICS.md b/SEMANTICS.md new file mode 100644 index 0000000000..53b8950e7e --- /dev/null +++ b/SEMANTICS.md @@ -0,0 +1,370 @@ +# 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 receive `DepsMut` and 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. + +## 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 (somewhat under-documented) +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 (and after +they leave). + +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 (typically every 5s or so), 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/v1beta1/tx.proto#L76-L89), +routes it to +[`Keeper.Execute`](https://github.com/CosmWasm/wasmd/blob/master/x/wasm/keeper/keeper.go#L311-L355), +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 +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { } +``` + +With `DepsMut`, this can read and write to the backing `Storage`, as well as use +the `Api` to validate addresses, and `Query` the state of other contracts or +native modules. Once it is done, it returns either `Ok(Response)` or +`Err(ContractError)`. Let's examine what happens next: + +If it returns `Err`, this error is converted to a string representation +(`err.to_string()`), and this is returned to the SDK module. _All state changes +are reverted_ and `x/wasm` returns this error message, which will _generally_ +(see submessage exception below) abort the transaction, and return this same +error message to the external caller. + +If it returns `Ok`, the `Response` object is parsed and processed. Let's look at +the parts here: + +```rust +pub struct Response +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + /// Optional list of "subcalls" to make. These will be executed in order + /// (and this contract's subcall_response entry point invoked) + /// *before* any of the "fire and forget" messages get executed. + pub submessages: Vec>, + /// After any submessages are processed, these are all dispatched in the host blockchain. + /// If they all succeed, then the transaction is committed. If any fail, then the transaction + /// and any local contract state changes are reverted. + pub messages: Vec>, + /// The attributes that will be emitted as part of a "wasm" event + pub attributes: Vec, + pub data: Option, +} +``` + +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. Note that the `ResultHash` includes +only the `Code` (non-zero meaning error) and `Result` (data) from the +transaction. Events and log are available via queries, but there are no +light-client proofs possible. + +If the contract sets `data`, this will be returned in the `result` field. +`attributes` is a list of `{key, value}` pairs which will be +[appended to a default event](https://github.com/CosmWasm/wasmd/blob/master/x/wasm/types/types.go#L302-L321). +The final result looks like this to the client: + +```json +{ + "type": "wasm", + "attributes": [ + { "key": "contract_addr", "value": "cosmos1234567890qwerty" }, + { "key": "custom-key-1", "value": "custom-value-1" }, + { "key": "custom-key-2", "value": "custom-value-2" } + ] +} +``` + +### Dispatching Messages + +Now let's move onto the `messages` field. Some contracts are fine only talking +with themselves, such as a cw20 contract just adjusting its balances on +transfers. But many want to move tokens (native or cw20) or call into other +contracts for more complex actions. This is where messages come in. We return +[`CosmosMsg`, which is a serializable representation](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/results/cosmos_msg.rs#L18-L40) +of any external call the contract can make. It looks something like this (with +`stargate` feature flag enabled): + +```rust +pub enum CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + Bank(BankMsg), + /// This can be defined by each blockchain as a custom extension + Custom(T), + Staking(StakingMsg), + Distribution(DistributionMsg), + Stargate { + type_url: String, + value: Binary, + }, + Ibc(IbcMsg), + Wasm(WasmMsg), +} +``` + +If a contract returns two messages - M1 and M2, these will both be parsed and +executed in `x/wasm` _with the permissions of the contract_ (meaning +`info.sender` will be the contract not the original caller). If they return +success, they will emit a new event with the custom attributes, the `data` field +will be ignored, and any messages they return will also be processed. If they +return an error, the parent call will return an error, thus rolling back state +of the whole transaction. + +Note that the messages are executed _depth-first_. This means if contract A +returns M1 (`WasmMsg::Execute`) and M2 (`BankMsg::Send`), and contract B (from +the `WasmMsg::Execute`) returns N1 and N2 (eg. `StakingMsg` and +`DistributionMsg`), the order of execution would be **M1, N1, N2, M2**. + +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](https://docs.cosmwasm.com/0.13/architecture/actor.html). + +### Submessages + +As of CosmWasm 0.14 (April 2021), we have added yet one more way to dispatch +calls from the contract. 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`, but then you need to store the address of +the newly created contract in the caller. With `submessages`, this is now +possible. It also solves a similar use-case of capturing the error results, so +if you execute a message from eg. 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` as above, but it wraps it inside a `SubMsg` +envelope: + +```rust +pub struct SubMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + pub id: u64, + pub msg: CosmosMsg, + pub gas_limit: Option, + pub reply_on: ReplyOn, +} + +pub enum ReplyOn { + /// Always perform a callback after SubMsg is processed + Always, + /// Only callback if SubMsg returned an error, no callback on success case + Error, + /// Only callback if SubMsg was successful, no callback on error case + Success, +} +``` + +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` is processed as normal (an event is added to the +current EventManager, messages and submessages are executed). 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_ + +#### Handling the Reply + +In order to make use of `submessages`, the calling contract must have an extra +entry point: + +```rust +#[entry_point] +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { } + +pub struct Reply { + pub id: u64, + /// ContractResult is just a nicely serializable version of `Result` + pub result: ContractResult, +} + +pub struct SubcallResponse { + pub events: Vec, + pub data: Option, +} +``` + +After the `submessage` 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 as well (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 `submessage` 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`, which will be processed as normal - +events added to the EventManager, and all `messages` and `submessages` +dispatched as described above. + +The one _critical difference_ with `reply`, is that we _do not drop data_. If +`reply` returns `data: Some(value)` in the `Response` 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 submessages all setting this, only the last one is used (they all +overwrite any previous `data` value). + +#### Order and Rollback + +Submessages (and their replies) are all executed before any `messages`. They +also follow the _depth first_ rules as with `messages`. Here is a simple +example. Contract A returns submessages S1 and S2, and message M1. Submessage S1 +returns message 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 submesssage - (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` 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_ `Querier` +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://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/query/mod.rs#L27-L48) +that represents all possible calls, and then pass that over FFI to the runtime, +where it is interpretted in the `x/wasm` SDK module. This is extensible with +blockchain-specific custom queries just like `CosmosMsg` accepts custom results. +Also note the ability to perform raw protobuf "Stargate" queries: + +```rust +pub enum QueryRequest { + Bank(BankQuery), + Custom(C), + Staking(StakingQuery), + Stargate { + /// this is the fully qualified service path used for routing, + /// eg. custom/cosmos_sdk.x.bank.v1.Query/QueryBalance + path: String, + /// this is the expected protobuf message type (not any), binary encoded + data: Binary, + }, + Ibc(IbcQuery), + Wasm(WasmQuery), +} +``` + +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://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/traits.rs#L148-L314), +which wraps a `Querier` and exposes a lot of convenience methods that just use +`QueryRequest` and `Querier.raw_query` under the hood. + +You can read a longer explanation of the +[`Querier` design in our docs](https://docs.cosmwasm.com/0.13/architecture/query.html).