Skip to content

Commit ef97851

Browse files
author
Tomasz Kulik
committed
Move wasmvm/spec and cosmwasm/SEMANTICS.md here
1 parent 56c3bfb commit ef97851

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed

src/pages/core/semtantics.mdx

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
---
2+
tags: ["core"]
3+
---
4+
5+
# Contract Semantics
6+
7+
## TODO tkulik: Check if the info does not overlap with already created docs
8+
9+
This document aims to clarify the semantics of how a CosmWasm contract interacts with its
10+
environment. There are two main types of actions: _mutating_ actions, which are able to modify the
11+
state of the blockchain, and _query_ actions, which are run on a single node with read-only access
12+
to the data.
13+
14+
## Definitions
15+
16+
**Contract** is as some wasm code uploaded to the system, initialized at the creation of the
17+
contract. This has no state except that which is contained in the wasm code (eg. static constants)
18+
19+
**Instance** is one instantiation of the contract. This contains a reference to the contract, as
20+
well as some "local" state to this instance, initialized at the creation of the instance. This state
21+
is stored in the kvstore, meaning a reference to the code plus a reference to the (prefixed) data
22+
store uniquely defines the smart contract.
23+
24+
Example: we could upload a generic "ERC20 mintable" contract, and many people could create
25+
independent instances of the same bytecode, where the local data defines the token name, the issuer,
26+
the max issuance, etc.
27+
28+
- First you **create** a _contract_
29+
- Then you **instantiate** an _instance_
30+
- Finally users **invoke** the _instance_
31+
32+
## Execution
33+
34+
In the section below, we will discuss how the `execute` call works, but the same semantics apply to
35+
any other _mutating_ action - `instantiate`, `migrate`, `sudo`, etc.
36+
37+
### SDK Context
38+
39+
Before looking at CosmWasm, we should look at the semantics enforced by the blockchain framework we
40+
integrate with - the [Cosmos SDK](https://v1.cosmos.network/sdk). It is based upon the
41+
[Tendermint BFT](https://tendermint.com/core/) Consensus Engine. Let us first look how they process
42+
transactions before they arrive in CosmWasm.
43+
44+
First, the Tendermint engine will seek 2/3+ consensus on a list of transactions to be included in
45+
the next block. This is done _without executing them_. They are simply subjected to a minimal
46+
pre-filter by the Cosmos SDK module, to ensure they are validly formatted transactions, with
47+
sufficient gas fees, and signed by an account with sufficient fees to pay it. Notably, this means
48+
many transactions that error may be included in a block.
49+
50+
Once a block is committed, the transactions are then fed to the Cosmos SDK sequentially in order to
51+
execute them. Each one returns a result or error along with event logs, which are recorded in the
52+
`TxResults` section of the next block. The `AppHash` (or merkle proof or blockchain state) after
53+
executing the block is also included in the next block.
54+
55+
The Cosmos SDK `BaseApp` handles each transaction in an isolated context. It first verifies all
56+
signatures and deducts the gas fees. It sets the "Gas Meter" to limit the execution to the amount of
57+
gas paid for by the fees. Then it makes an isolated context to run the transaction. This allows the
58+
code to read the current state of the chain (after the last transaction finished), but it only
59+
writes to a cache, which may be committed or rolled back on error.
60+
61+
A transaction may consist of multiple messages and each one is executed in turn under the same
62+
context and same gas limit. If all messages succeed, the context will be committed to the underlying
63+
blockchain state and the results of all messages will be stored in the `TxResult`. If one message
64+
fails, all later messages are skipped and all state changes are reverted. This is very important for
65+
atomicity. That means Alice and Bob can both sign a transaction with 2 messages: Alice pays Bob 1000
66+
ATOM, Bob pays Alice 50 ETH, and if Bob doesn't have the funds in his account, Alice's payment will
67+
also be reverted. This is just like a DB Transaction typically works.
68+
69+
[`x/wasm`](https://github.com/CosmWasm/wasmd/tree/master/x/wasm) is a custom Cosmos SDK module,
70+
which processes certain messages and uses them to upload, instantiate, and execute smart contracts.
71+
In particular, it accepts a properly signed
72+
[`MsgExecuteContract`](https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1/tx.proto),
73+
routes it to
74+
[`Keeper.Execute`](https://github.com/CosmWasm/wasmd/blob/master/x/wasm/keeper/keeper.go), which
75+
loads the proper smart contract and calls `execute` on it. Note that this method may either return a
76+
success (with data and events) or an error. In the case of an error here, it will revert the entire
77+
transaction in the block. This is the context we find ourselves in when our contract receives the
78+
`execute` call.
79+
80+
### Basic Execution
81+
82+
When we implement a contract, we provide the following entry point:
83+
84+
```rust template="core"
85+
#[cfg_attr(not(feature = "library"), entry_point)]
86+
pub fn execute(
87+
deps: DepsMut,
88+
env: Env,
89+
info: MessageInfo,
90+
msg: ExecuteMsg,
91+
) -> Result<Response, ContractError> {
92+
// [...]
93+
}
94+
```
95+
96+
With [`DepsMut`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.DepsMut.html), this can
97+
read and write to the backing
98+
[`Storage`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html), as well as use the
99+
[`Api`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html) to validate addresses, and
100+
use [`QuerierWrapper`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.QuerierWrapper.html)
101+
the state of other contracts or native modules. Once it is done, it returns either `Ok(Response)` or
102+
`Err(ContractError)`.
103+
104+
If it returns `Err`, this error is converted to a string representation, and it's returned to the
105+
SDK module. _All state changes are reverted_ and `x/wasm` returns this error message, which will
106+
_generally_ abort the transaction, and return the error message to the external caller.
107+
108+
If it returns `Ok`, the
109+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) object is parsed
110+
and processed.
111+
112+
In the Cosmos SDK, a transaction returns a number of events to the user, along with an optional data
113+
"result". This result is hashed into the next block hash to be provable and can return some
114+
essential state (although in general client apps rely on Events more). This result is more commonly
115+
used to pass results between contracts or modules in the sdk.
116+
117+
### Dispatching Submessages
118+
119+
Now let's move onto the `messages` field of the
120+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html). Some contracts
121+
are fine only talking with themselves. But many want to move tokens or call into other contracts for
122+
more complex actions. This is where messages come in. We return
123+
[`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html), which is a
124+
serializable representation of any external call the contract can make.
125+
126+
This may be hard to understand at first. "Why can't I just call another contract?", you may ask.
127+
However, we do this to prevent one of most widespread and hardest to detect security holes in
128+
Ethereum contracts - reentrancy. We do this by following the actor model, which doesn't nest
129+
function calls, but returns messages that will be executed later. This means all state that is
130+
carried over between one call and the next happens in storage and not in memory. For more
131+
information on this design, I recommend you read
132+
[our docs on the Actor Model](architecture/actor-model.mdx).
133+
134+
A common request was the ability to get the result from one of the messages you dispatched. For
135+
example, you want to create a new contract with
136+
[`WasmMsg::Instantiate`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.WasmMsg.html#variant.Instantiate),
137+
but then you need to store the address of the newly created contract in the caller. this is possible
138+
with `messages`. It also solves a similar use-case of capturing the error results, so if you execute
139+
a message from e.g. a cron contract, it can store the error message and mark the message as run,
140+
rather than aborting the whole transaction. It also allows for limiting the gas usage of the
141+
submessage (this is not intended to be used for most cases, but is needed for eg. the cron job to
142+
protect it from an infinite loop in the submessage burning all gas and aborting the transaction).
143+
144+
This makes use of
145+
[`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html) as above, but it
146+
wraps it inside a [`SubMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.SubMsg.html)
147+
envelope.
148+
149+
What are the semantics of a submessage execution. First, we create a sub-transaction context around
150+
the state, allowing it to read the latest state written by the caller, but write to yet-another
151+
cache. If `gas_limit` is set, it is sandboxed to how much gas it can use until it aborts with
152+
`OutOfGasError`. This error is caught and returned to the caller like any other error returned from
153+
contract execution (unless it burned the entire gas limit of the transaction). What is more
154+
interesting is what happens on completion.
155+
156+
If it return success, the temporary state is committed (into the caller's cache), and the
157+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) is processed as
158+
normal. Once the response is fully processed, this may then be intercepted by the calling contract
159+
(for `ReplyOn::Always` and `ReplyOn::Success`). On an error, the subcall will revert any partial
160+
state changes due to this message, but not revert any state changes in the calling contract. The
161+
error may then be intercepted by the calling contract (for `ReplyOn::Always` and `ReplyOn::Error`).
162+
_In this case, the messages error doesn't abort the whole transaction_
163+
164+
Note, that error doesn't abort the whole transaction _if and only if_ the `reply` is called - so in
165+
case of `ReplyOn::Always` and `ReplyOn::Error`. If the submessage is called with `ReplyOn::Success`
166+
(or `ReplyOn::Never`, which makes it effectively a normal message), the error in subsequent call
167+
would result in failing whole transaction and not commit the changes for it. The rule here is as
168+
follows: if for any reason you want your message handling to succeed on submessage failure, you
169+
always have to reply on failure.
170+
171+
Obviously - on the successful processing of sub-message, if the reply is not called (in particular
172+
`ReplyOn::Error`), the whole transaction is assumed to succeed, and is committed.
173+
174+
#### Handling the Reply
175+
176+
In order to make use of `messages`, the calling contract must have an extra entry point:
177+
178+
```rust filename="contract.rs" template="core"
179+
#[cfg_attr(not(feature = "library"), entry_point)]
180+
pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> StdResult<Response> {
181+
// [...]
182+
}
183+
```
184+
185+
After the `message` is finished, the caller will get a chance to handle the result. It will get the
186+
original `id` of the subcall so it can switch on how to process this, and the `Result` of the
187+
execution, both success and error. Note that it includes all events returned by the submessage,
188+
which applies to native sdk modules (like Bank) as well as the data returned from below. This and
189+
the original call id provide all context to continue processing it. If you need more state, you must
190+
save some local context to the store (under the `id`) before returning the `message` in the original
191+
`execute`, and load it in `reply`. We explicitly prohibit passing information in contract memory, as
192+
that is the key vector for reentrancy attacks, which are a large security surface area in Ethereum.
193+
194+
The `reply` call may return `Err` itself, in which case it is treated like the caller errored, and
195+
aborting the transaction. However, on successful processing, `reply` may return a normal
196+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html), which will be
197+
processed as normal - events added to the EventManager, and all `messages` dispatched as described
198+
above.
199+
200+
## TODO tkulik: This paragraph is related to the issue from first PR
201+
202+
The one _critical difference_ with `reply`, is that we _do not drop data_. If `reply` returns
203+
`data: Some(value)` in the
204+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) object, we will
205+
overwrite the `data` field returned by the caller. That is, if `execute` returns
206+
`data: Some(b"first thought")` and the `reply` (with all the extra information it is privy to)
207+
returns `data: Some(b"better idea")`, then this will be returned to the caller of `execute` (either
208+
the client or another transaction), just as if the original `execute` and returned
209+
`data: Some(b"better idea")`. If `reply` returns `data: None`, it will not modify any previously set
210+
data state. If there are multiple messages all setting this, only the last one is used (they all
211+
overwrite any previous `data` value). As a consequence, you can use `data: Some(b"")` to clear
212+
previously set data. This will be represented as a JSON string instead of `null` and handled as any
213+
other `Some` value.
214+
215+
#### Order and Rollback
216+
217+
## TODO tkulik: make sure that the order is properly described here
218+
219+
Submessages follow a _depth first_ order rules, with their replies considered as an immediate
220+
additional message call. Here is a simple example. Contract **A** returns messages **S1** and
221+
**S2**, and message **M1**. Submessage **S1** returns submessage **N1**. The order will be: **S1,
222+
N1, reply(S1), S2, reply(S2), M1**.
223+
224+
Please keep in mind that submessage `execution` and `reply` can happen within the context of another
225+
submessage. For example `contract-A--submessage --> contract-B--submessage --> contract-C`. Then
226+
`contract-B` can revert the state for `contract-C` and itself by returning `Err` in the submessage
227+
`reply`, but not revert contract-A or the entire transaction. It just ends up returning `Err` to
228+
contract-A's `reply` function.
229+
230+
Note that errors are not handled with `ReplyOn::Success`, meaning, in such a case, an error will be
231+
treated just like a normal `message` returning an error. This diagram may help explain. Imagine a
232+
contract returned two submesssages - (a) with `ReplyOn::Success` and (b) with `ReplyOn::Error`:
233+
234+
| processing a) | processing b) | reply called | may overwrite result from reply | note |
235+
| ------------- | ------------- | ------------ | ------------------------------- | ------------------------------------------------- |
236+
| ok | ok | a) | a) | returns success |
237+
| err | err | none | none | returns error (abort parent transaction) |
238+
| err | ok | none | none | returns error (abort parent transaction) |
239+
| ok | err | a)b) | a)b) | if both a) and b) overwrite, only b) will be used |
240+
241+
## Query Semantics
242+
243+
Until now, we have focused on the
244+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) object, which
245+
allows us to execute code in other contracts via the actor model. That is, each contract is run
246+
sequentially, one after another, and no nested calls are possible. This is essential to avoid
247+
reentrancy, which is when calling into another contract can change my state while I am in the middle
248+
of a transaction.
249+
250+
However, there are many times we need access to information from other contracts in the middle of
251+
processing, such as determining the contract's bank balance before sending funds. To enable this, we
252+
have exposed the _read only_
253+
[`QuerierWrapper`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.QuerierWrapper.html) to
254+
enable _synchronous_ calls in the middle of the execution. By making it read-only (and enforcing
255+
that in the VM level), we can prevent the possibility of reentrancy, as the query cannot modify any
256+
state or execute our contract.
257+
258+
When we "make a query", we serialize a
259+
[`QueryRequest` struct](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.QueryRequest.html)
260+
that represents all possible calls, and then pass that over FFI to the runtime, where it is
261+
interpreted in the `x/wasm` SDK module. This is extensible with blockchain-specific custom queries
262+
just like [`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html)
263+
accepts custom results.
264+
265+
While this is flexible and needed encoding for the cross-language representation, this is a bit of
266+
mouthful to generate and use when I just want to find my bank balance. To help that, we often use
267+
[`QuerierWrapper`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.QuerierWrapper.html).

0 commit comments

Comments
 (0)