Skip to content

Commit e53cdac

Browse files
committed
Added actor model page
1 parent 4bace57 commit e53cdac

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[reentrancy issues]: https://ethereum.org/en/developers/docs/smart-contracts/security/#reentrancy
2+
[enum dispatch]: ../conventions/enum-dispatch
3+
[CEI pattern (Checks, Effects, Interactions)]: https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html
4+
5+
# Actor model
6+
7+
CosmWasm, at its core, is built around the actor model. This prevents some common pitfalls which,
8+
for example, Ethereum smart contracts had to fix over the years. The issues mostly revolved around
9+
[reentrancy issues] where we could, for example, call out to another contract before finishing the
10+
current execution.
11+
12+
::: tip :bulb: Tip
13+
If you are already familiar with the actor model through, for example, programming in languages
14+
such as Erlang or Elixir, you can skip this page.
15+
:::
16+
17+
While the term "actor model" sounds fancy, it is actually quite simple. Each contract is an "actor"
18+
and _plays_ a role in the system (pun intended).
19+
20+
Each actor has its own state and can only be interacted with via messages. This means a contract can
21+
only interact with the outside world via messages and only manipulate its own state.
22+
23+
Picture two people living in two houses, each house has a mailbox. If person A wants to talk to
24+
person B, they write a letter, address it to person B, and send it off. The mailman (or in this
25+
case, the chain) will put the letter in person B's mailbox.
26+
27+
Person B will then read the letter and can choose to write a response, address it to person A with a
28+
note indicating that this is a reply, and send it off once again.
29+
30+
No direct interaction is happening here, nobody visits each other's house which would allow them to
31+
rummage around in their belongings (state). They simply exchange messages and put everything the
32+
other side needs to know into a message.
33+
34+
```mermaid
35+
sequenceDiagram
36+
participant A as Person A
37+
participant B as Person B
38+
note right of A: id: 123<br/>Hello!
39+
A ->> B:  
40+
note left of B: id: 567<br/>Hello back!<br/>reply-for-id: 123
41+
B ->> A:  
42+
```
43+
44+
As you can see in the simple graphic above, they simply exchange messages addressed to each other,
45+
and the messages reference previous messages if need be.
46+
47+
::: tip :bulb: Tip
48+
In CosmWasm you can only pass a single message type to a contract endpoint. If you are wondering
49+
how to handle multiple message types, check out the [enum dispatch] page.
50+
:::
51+
52+
But how does that fix reentrancy? In CosmWasm, you can only send out messages at the end of a
53+
contract execution as part of the response. This ensures you have already written everything to the
54+
state, meaning the state can't suddenly change mid-execution and make your contract exhibit buggy
55+
behaviour.
56+
57+
CosmWasm effectively forces you to follow the [CEI pattern (Checks, Effects, Interactions)] while
58+
other similar systems only have this as a "best practice".
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
tags: ["core", "architecture"]
3+
---
4+
5+
# Transactions
6+
7+
Every contract invocation is wrapped into a transaction. If you know about transactions in SQL
8+
databases, you can consider them as the same basic concept. You execute multiple operations in a
9+
single transaction, and if one of them fails, the whole transaction is rolled back.
10+
11+
In our case, these operations are invocations of contract entrypoints. If one of the invocations in
12+
the chain fails, the whole transaction is usually rolled back. Failing in this context means that
13+
the contract entrypoint returns an error or panics.
14+
15+
## Dispatching Submessages
16+
17+
Now let's move onto the `messages` field of the
18+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html). Some contracts
19+
are fine only talking with themselves. But many want to move tokens or call into other contracts for
20+
more complex actions. This is where messages come in. We return
21+
[`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html), which is a
22+
serializable representation of any external call the contract can make.
23+
24+
This may be hard to understand at first. "Why can't I just call another contract?", you may ask.
25+
However, we do this to prevent one of the most widespread and hardest to detect security holes in
26+
Ethereum contracts - reentrancy. We do this by following the actor model, which doesn't nest
27+
function calls, but returns messages that will be executed later. This means all state that is
28+
carried over between one call and the next happens in storage and not in memory. For more
29+
information on this design, I recommend you read [our docs on the Actor Model](actor-model.mdx).
30+
31+
A common request was the ability to get the result from one of the messages you dispatched. For
32+
example, you want to create a new contract with
33+
[`WasmMsg::Instantiate`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.WasmMsg.html#variant.Instantiate),
34+
but then you need to store the address of the newly created contract in the caller. This is possible
35+
with `messages` and replies. This makes use of
36+
[`CosmosMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.CosmosMsg.html) as above, but it
37+
wraps it inside a [`SubMsg`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.SubMsg.html)
38+
envelope.
39+
40+
What are the semantics of a submessage execution? First, we create a sub-transaction context around
41+
the state, allowing it to read the latest state written by the caller, but write to yet-another
42+
cache. If `gas_limit` is set, it is sandboxed to how much gas it can use until it aborts with
43+
`OutOfGasError`. This error is caught and returned to the caller like any other error returned from
44+
contract execution (unless it burned the entire gas limit of the transaction).
45+
46+
If it returns success, the temporary state is committed (into the caller's cache), and the
47+
[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html) is processed as
48+
normal. Once the response is fully processed, this may then be intercepted by the calling contract
49+
(for `ReplyOn::Always` and `ReplyOn::Success`). On an error, the subcall will revert any partial
50+
state changes due to this message, but not revert any state changes in the calling contract. The
51+
error may then be intercepted by the calling contract (for `ReplyOn::Always` and `ReplyOn::Error`).
52+
In this case, the message's error doesn't abort the whole transaction.
53+
54+
Note, that error doesn't abort the whole transaction _if and only if_ the `reply` is called - so in
55+
case of `ReplyOn::Always` and `ReplyOn::Error`. If the submessage is called with `ReplyOn::Success`
56+
or `ReplyOn::Never`, the error in a subsequent call would result in failing whole transaction and
57+
not commit the changes for it. The rule here is as follows: if for any reason you want your message
58+
handling to succeed on submessage failure, you always have to reply on failure.
59+
60+
## Preventing rollbacks in case of failure
61+
62+
If you don't want your entire transaction to be rolled back in case of a failure, you can split the
63+
logic into multiple messages. This can be two contracts, a contract executing itself or a contract
64+
that sends a message to a Cosmos SDK module. Then use the `reply_on` field in the message you send.
65+
Set the field to one of the following values and instead of rolling back the transaction, you will
66+
receive a message containing the error:
67+
68+
- `ReplyOn::Always`
69+
- `ReplyOn::Error`
70+
71+
That way you can handle the error and decide what to do next, whether you want to propagate the
72+
error, retry the operation, ignore it, etc.
73+
74+
The default value `ReplyOn::Success` means the caller is not ready to handle an error in the message
75+
execution and the entire transaction is reverted on error.
76+
77+
## Order of execution and rollback procedure
78+
79+
Submessages handling follows _depth first_ order rules. Let's see the following example scenario:
80+
81+
```mermaid
82+
83+
sequenceDiagram
84+
Note over Contract1: Contract1 returns two submessages:<br/> 1. Execute Contract2<br/> 2. Execute Contract4
85+
Contract1->>Contract2: 1. Execute
86+
Note over Contract2: Contract2 returns one submessage:<br/> 1. Execute Contract3
87+
Contract2->>Contract3: 2. Execute
88+
Contract3->>Contract2: 3. Response
89+
Note over Contract2: Contract2 can handle the Response<br/>in the reply entrypoint or leave it
90+
Contract2->>Contract1: 4. Response
91+
Note over Contract1: Contract1 can handle the Response<br/>in the reply entrypoint or leave it
92+
Contract1->>Contract4: 5. Execute
93+
Contract4->>Contract1: 6. Response
94+
Note over Contract1: Contract1 can handle the Response<br/>in the reply entrypoint or leave it
95+
96+
```
97+
98+
**Note1:** The
99+
[msg_responses](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.SubMsgResponse.html#structfield.msg_responses)
100+
of the response are not forwarded down the call path. It means that for e.g. if `Contract2` will not
101+
explicitly handle response from `Contract3` and forward any data, then `Contract1` will never learn
102+
about results from `Contract3`.
103+
104+
**Note2:** If `Contract2` returns an error, the error message can be handled by the `Contract1`
105+
reply entry-point and prevent the whole transaction from rollback. In such a case only the
106+
`Contract2` and `Contract3` states changes are reverted.

0 commit comments

Comments
 (0)