From 9f61b957779bf9590c85d59dcbfe5ae7545943ca Mon Sep 17 00:00:00 2001 From: Z <86721028+Zagita21@users.noreply.github.com> Date: Tue, 4 Jul 2023 19:49:00 +0100 Subject: [PATCH 1/2] Update README.md improve English --- src/README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/README.md b/src/README.md index e58fb45..330895f 100644 --- a/src/README.md +++ b/src/README.md @@ -1,25 +1,24 @@ # Introduction This book is a guide for creating CosmWasm smart contracts. It will lead you -step by step, and explain relevant topics from the easiest to the trickier -ones. +step by step through the process, and explain all of the relevant topics from +the easiest to the trickiest ones. -The idea of the book is not only to tell you about smart contracts API but also -to show you how to do it in a clean and maintainable way. We will show you -patterns that CosmWasm creators established and encouraged you to use. +The idea of the book is not only to describe the API for smart contracts but also +to show you how to use them in a clean and maintainable way. We will show you +patterns that CosmWasm creators have established and encourage you to use as well. ## Prerequirements -This book explores CosmWasm smart contracts. It is not a Rust tutorial, and it -assumes basic Rust knowledge. As you will probably learn it alongside this -book, I strongly recommend grasping the language itself first. You can find -great resources to start with Rust on [Learn -Rust](https://www.rust-lang.org/learn) page. +This book explores CosmWasm smart contracts. It is not a Rust tutorial, and +assumes basic Rust knowledge. We strongly recommend ensuring you have a good +grasp of the language itself first. You can find great resources for starting +with Rust on [Learn Rust](https://www.rust-lang.org/learn) page. ## CosmWasm API documentation -This is the guide-like documentation. If you are looking for the API -documentation, you may be interested in checking one of the following: +This documentation is presented as a guide. If you are looking for the API +documentation, you may be interested in checking out one of the following: - [cosmwasm-std](https://crates.io/crates/cosmwasm-std) - [cw-storage-plus](https://crates.io/crates/cw-storage-plus) From d7e9b6c9e6f580c4742d8f331e50c0e2332aa0ba Mon Sep 17 00:00:00 2001 From: Z Date: Sun, 9 Jul 2023 23:14:11 +0100 Subject: [PATCH 2/2] whole book review: two passes --- src/README.md | 8 +- src/actor-model.md | 7 +- src/actor-model/actors-in-blockchain.md | 229 ++++++------- src/actor-model/contract-as-actor.md | 415 +++++++++++------------ src/actor-model/idea.md | 431 +++++++++++------------- src/basics.md | 5 +- src/basics/building-contract.md | 36 +- src/basics/entry-points.md | 65 ++-- src/basics/events.md | 46 ++- src/basics/execute.md | 98 +++--- src/basics/fp-types.md | 23 +- src/basics/funds.md | 101 +++--- src/basics/good-practices.md | 79 +++-- src/basics/multitest-intro.md | 63 ++-- src/basics/query-testing.md | 62 ++-- src/basics/query.md | 149 ++++---- src/basics/rust-project.md | 14 +- src/basics/state.md | 68 ++-- src/cross-contract.md | 22 +- src/cross-contract/design.md | 45 ++- src/cross-contract/fixing-admin.md | 86 +++-- src/cross-contract/map-storage.md | 133 ++++---- src/cross-contract/working-with-time.md | 79 ++--- src/setting-up-env.md | 34 +- src/wasmd-quick-start.md | 10 +- 25 files changed, 1091 insertions(+), 1217 deletions(-) diff --git a/src/README.md b/src/README.md index 330895f..320655a 100644 --- a/src/README.md +++ b/src/README.md @@ -1,12 +1,12 @@ # Introduction This book is a guide for creating CosmWasm smart contracts. It will lead you -step by step through the process, and explain all of the relevant topics from -the easiest to the trickiest ones. +step by step through the process and explain all of the relevant topics, from +the easiest to the trickiest. The idea of the book is not only to describe the API for smart contracts but also -to show you how to use them in a clean and maintainable way. We will show you -patterns that CosmWasm creators have established and encourage you to use as well. +to show you how to use them in a clean and maintainable way. We will show you the +patterns that CosmWasm creators have established and we encourage you to use as well. ## Prerequirements diff --git a/src/actor-model.md b/src/actor-model.md index ce78331..563c460 100644 --- a/src/actor-model.md +++ b/src/actor-model.md @@ -1,9 +1,8 @@ # Actor model -This section describes the fundaments of CosmWasm smart contracts architecture, which determines how do they communicate -with each other. I want to go through this before teaching step by step how to create multiple contracts relating to each -other, to give you a grasp of what to expect. Don't worry if it will not be clear after the first read - I suggest going -through this chapter once now and maybe giving it another take in the future when you know the practical part of this. +This section describes the fundamentals of the architecture of CosmWasm smart contracts, which determines how they communicate +with each other. It is useful to go through this to get an idea of what to expect, before go through in detail how to create multiple contracts that relate to each other. Don't worry if it's not totally clear after the first read. We suggest going +through this chapter once now and maybe giving it another review once you are familiar with the practical part. The whole thing described here is officially documented in the [SEMANTICS.md](https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md), of the `cosmwasm` repository. diff --git a/src/actor-model/actors-in-blockchain.md b/src/actor-model/actors-in-blockchain.md index 178aebd..de5f9c0 100644 --- a/src/actor-model/actors-in-blockchain.md +++ b/src/actor-model/actors-in-blockchain.md @@ -1,67 +1,61 @@ # Actors in blockchain -Previously we were talking about actors mostly in the abstraction of any -blockchain-specific terms. However, before we would dive into the code, we need -to establish some common language, and to do so we would look at contracts from -the perspective of external users, instead of their implementation. +In the previius section we discussed actors mostly in the abstract, staying clear of any +blockchain-specific terms. However, we need to establish some common language before we dive into the code. To do so we shall look at contracts from +the perspective of external users, rather than their implementation. -In this part, I would use the `wasmd` binary to communicate with the malaga -testnet. To properly set it up, check the [Quick start with +In this section, we shall use the `wasmd` binary to communicate with the +testnet. To properly set it up, check out [Quick start with `wasmd`](../wasmd-quick-start.md). ## Blockchain as a database -It is kind of starting from the end, but I would start with the state part of -the actor model. Relating to traditional systems, there is one particular thing -I like to compare blockchain with - it is a database. +Although it is in some regards starting from the end, we'll start by looking at the state part of +the Actor Model. Insofar as it relates to traditional systems, there is one particular thing +we can directly compare blockchain with - a database! -Going back to the previous section we learned that the most important part of +In the previous section we learned that the most important part of a contract is its state. Manipulating the state is the only way to persistently -manifest work performed to the world. But What is the thing which purpose is to -keep the state? It is a database! - -So here is my (as a contract developer) point of view on contracts: it is a distributed -database, with some magical mechanisms to make it democratic. Those "magical -mechanisms" are crucial for BC's existence and they make they are reasons why even -use blockchain, but they are not relevant from the contract creator's point of -view - for us, everything that matters is the state. - -But you can say: what about the financial part?! Isn't blockchain (`wasmd` in particular) -the currency implementation? With all of those gas costs, sending funds seems -very much like a money transfer, not database updates. And yes, you are kind of right, -but I have a solution for that too. Just imagine, that for every native token (by -"native tokens" we meant tokens handled directly by blockchain, in contradiction -to for example cw20 tokens) there is a special database bucket (or table if you prefer) -with mapping of address to how much of a token the address possesses. You can query -this table (querying for token balance), but you cannot modify it directly. To modify -it you just send a message to a special build-in bank contract. And everything -is still a database. - -But if blockchain is a database, then where are smart contracts stored? -Obviously - in the database itself! So now imagine another special table - this -one would contain a single table of code-ids mapped to blobs of wasm binaries. And -again - to operate on this table, you use "special contract" which is not accessible -from another contract, but you can use it via `wasmd` binary. - -Now there is a question - why do I even care about BC being a DB? So the reason -is that it makes reasoning about everything in blockchain very natural. Do you -remember that every message in the actor model is transactional? It perfectly -matches traditional database transactions (meaning: every message starts a new -transaction)! Also, when we later talk about migrations, it would turn out, that +manifest work performed to the world. But what else shares the purpose of maintaining a state? A database! + +As a contract developer, the author's point of view is that we can think of it as a distributed +database with some magical mechanisms added to make it democratic. Those "magical +mechanisms", although crucial for a blockchain's existence (they are in fact the reasons why we even +use a blockchain), are not relevant from the contract creator's point of +view. For us, all that matters is the state. + +But you may protest: what about the financial part?! Isn't a blockchain (`wasmd` in particular) +a currency implementation? Indeed, with all of those gas costs, sending funds certainly can seem +very much more like a money transfer than a database update. And yes, you'd be somewhat right, +although there is an argument to this too. Imagine that for every native token (by +"native tokens" we meant tokens handled directly by the blockchain as opposed to cw20 contract tokens, for example) there is a special database bucket/table that maps addresses to token balances (ie how much of a token the address owns). You can query +this table (query for token balance), but you cannot modify it directly. To modify +it you must send a message to a special build-in bank contract. Looked at like this, everything +is still just a database. + +But if a blockchain is a database, then where are smart contracts stored? +Well, obviously - in the database itself! So now imagine another special table - one containing a single table of code-ids mapped to blobs of wasm binaries. To operate on this table, you use a "special contract" which is not accessible +from another contract, but instead via the `wasmd` binary. + +You may be asking: why do I even care about viewing a blockchain as a database? The reason +is that it makes reasoning about everything in blockchain very much more natural. Do you +recall that every message in the Actor Model is transactional? It perfectly +matches traditional database transactions i.e. every message starts a new +transaction! Also, when we talk about migrations later, it will turn out that migrations in CosmWasm are very much equivalents of schema migrations in -traditional databases. +traditional databases! -So, the thing to remember - blockchain is very similar to a database, having some -specially reserved tables (like native tokens, code repository), with a special +So, the thing to keep in mind - a blockchain is very similar to a database, having some +specially reserved tables (like native tokens, code repository, etc.), with a special bucket created for every contract. A contract can look at every table in every -bucket in the whole blockchain, but it can modify the only one he created. +bucket on the whole blockchain, but it can only modify its own (the one it created). ## Compile the contract -I will not go into the code for now, but to start with something we need compiled +We won't go directly into the code just yet, but to start we'll need at least a compiled contract binary. The `cw4-group` contract from -[cw-plus](https://github.com/CosmWasm/cw-plus) is simple enough to work with, for -now, so we will start with compiling it. Start with cloning the repository: +[cw-plus](https://github.com/CosmWasm/cw-plus) is simple enough to work with for +now, let's start by compiling it. First clone the repository: ```bash $ git clone git@github.com:CosmWasm/cw-plus.git @@ -83,13 +77,13 @@ Your final binary should be located in the ## Contract code When the contract binary is built, the first interaction with CosmWasm is uploading -it to the blockchain (assuming you have your wasm binary in the working directory): +it to the blockchain. Assuming you have your wasm binary in the working directory: ```bash $ wasmd tx wasm store ./cw4-group.wasm --from wallet $TXFLAG -y -b block ``` -As a result of such an operation you would get json output like this: +As a result of this operation you should get json output that looks like this: ``` .. @@ -103,46 +97,44 @@ logs: type: store_code ``` -I ignored most of not fields as they are not relevant for now - what we care -about is the event emitted by blockchain with information about `code_id` of -stored contract - in my case the contract code was stored in blockchain under -the id of `1069`. I can now look at the code by querying for it: +We've ignored most of the fields as they are not relevant for now - what we care +about is the event emitted by the blockchain with information about the `code_id` of +stored contract. In this case the contract code was stored in blockchain under +the id of `1069`. We can now look at the code by querying for it: ```bash $ wasmd query wasm code 1069 code.wasm ``` -And now the important thing - the contract code is not an actor. So, what is a -contract code? I think that the easiest way to think about that is a `class` or -a `type` in programming. It defines some stuff about what can be done, but the +And now the important thing - the contract code is not an actor! So, what is exactly is +contract code then? Perhaps the easiest way to think about it is as similar to a `class` or +a `type` in programming. It provides a blueprint for what can be done, but the class itself is in most cases not very useful unless we create an instance -of a type, on which we can call class methods. So now let's move forward to +of a type on which we can call its class methods. So now let's move on to using instances of such contract classes. ## Contract instance -Now we have a contract code, but what we want is an actual contract itself. -To create it, we need to instantiate it. Relating to analogy to programming, -instantiation is calling a constructor. To do that, I would send an -instantiate message to my contract: +Now we have our contract code, but what we want is an actual contract itself. +To create it, we need to instantiate it. Instantiation is analogous to calling a constructor in regular (object-oriented) programming. To do this, we need to send an instantiate message to the contract: ```bash $ wasmd tx wasm instantiate 1069 '{"members": []}' --from wallet --label "Group 1" --no-admin $TXFLAG -y ``` -What I do here is create a new contract and immediately call the `Instantiate` +What we've done here is to create a new contract and immediately call the `Instantiate` message on it. The structure of such a message is different for every contract code. In particular, the `cw4-group` Instantiate message contains two fields: -* `members` field which is the list of initial group members optional `admin` -* field which defines an address of who can add or remove - a group member +* `members` field, which is the list of initial group members +* `admin` an optional field that defines an address, specifying who can add or remove + group members -In this case, I created an empty group with no admin - so which could never -change! It may seem like a not very useful contract, but it serves us as a -contract example. +In this example, we've created an empty group with no admin - so it could never +change! It may seem not seem like a very useful contract, but it serves our needs as a first +example. -As the result of instantiating, I got the result: +As the result of instantiating, we get this: ``` .. @@ -158,28 +150,28 @@ logs: type: instantiate ``` -As you can see, we again look at `logs[].events[]` field, looking for -interesting event and extracting information from it - it is the common case. -I will talk about events and their attributes in the future but in general, -it is a way to notify the world that something happened. Do you remember the -KFC example? If a waiter is serving our dish, he would put a tray on the bar, -and she would yell (or put on the screen) the order number - this would be -announcing an event, so you know some summary of operation, so you can go and +As you can see, we once more look at the `logs[].events[]` field, looking for an +interesting event and extracting information from it. This is the common case. +We will talk more about events and their attributes later, but in general +they are ways to notify the world that something happened. Do you remember the +KFC example? If a waiter is serving our dish, they could put a tray on the bar and then +yell the order number (or put it on a screen). This would be +announcing an event, so that one can get some summary of the operation and possibly go and do something useful with it. -So, what use can we do with the contract? We obviously can call it! But first -I want to tell you about addresses. +So, what can we do with the contract? Well, obviously we can call it! But first +we need to discuss addresses. ## Addresses in CosmWasm -Address in CosmWasm is a way to refer to entities in the blockchain. There are -two types of addresses: contract addresses, and non-contracts. The difference -is that you can send messages to contract addresses, as there is some smart -contract code associated with them, and non-contracts are just users of the -system. In an actor model, contract addresses represent actors, and +An address in CosmWasm is a way to refer to entities on the blockchain. There are +two types of addresses: contract addresses, and non-contract addresses ("non-contracts"). The difference +is that you can also send messages to contract addresses, as there is some smart +contract code associated with them, while non-contracts are just ordinary users of the +system. In an Actor Model, contract addresses represent actors, and non-contracts represent clients of the system. -When operating with blockchain using `wasmd`, you also have an address - you +When operating with the blockchain using `wasmd`, you also have an address yourself - you got one when you added the key to `wasmd`: ```bash @@ -204,13 +196,13 @@ $ wasmd keys show wallet ``` Having an address is very important because it is a requirement for being able -to call anything. When we send a message to a contract it always knows the -address which sends this message so it can identify it - not to mention that -this sender is an address that would play a gas cost. +to call anything. When we send a message to a contract it always needs to know the +address of the entity that sent it (the sender) - not to mention that +this sender is an address that will pay any gas fees. ## Querying the contract -So, we have our contract, let's try to do something with it - query would be the +So, now we have our contract, let's try to do something with it - a query would be the easiest thing to do. Let's do it: ```bash @@ -219,31 +211,30 @@ data: members: [] ``` -The `wasm...` string is the contract address, and you have to substitute it with -your contract address. `{ "list_members": {} }` is query message we send to -contract. Typically, CW smart contract queries are in the form of a single JSON +The `wasm...` string is the contract address, and you should substitute it with +your own contract address. `{ "list_members": {} }` is the query message we send to the +contract. Typically, CosmWasm smart contract queries are given in the form of a single JSON object, with one field: the query name (`list_members` in our case). The value -of this field is another object, being query parameters - if there are any. -`list_members` query handles two parameters: `limit`, and `start_after`, which -are both optional and which support result pagination. However, in our case of -an empty group they don't matter. - -The query result we got is in human-readable text form (if we want to get the -JSON from - for example, to process it further with `jq`, just pass the -`-o json` flag). As you can see response contains one field: `members` which is +of this field is another object, holding query parameters (if there are any). The +`list_members` query handles two optional parameters: `limit`, and `start_after`, which support result pagination when used. However, in our case of +an empty group they aren't needed. + +The query result we got is in human-readable text form. Iif we wanted to get the +JSON from - for example, to process it further with `jq` - we would just pass the +`-o json` flag. As you can see, the response contains just one field: `members` which is an empty array. -So, can we do anything more with this contract? Not much. But let's try to do +So, can we do anything else with this contract? Not really. Let's try to do something with a new one! ## Executions to perform some actions The problem with our previous contract is that for the `cw4-group` contract, the only one who can perform executions on it is an admin, but our contract -doesn't have one. This is not true for every smart contract, but it is the +instance didn't have one. This is not a requirement for every smart contract, but it is the nature of this one. -So, let's make a new group contract, but this time we would +So, let's make a new group contract, but this time we will make ourselves an admin. First, check our wallet address: ```bash @@ -266,13 +257,13 @@ logs: type: instantiate ``` -You may ask, why do we pass some kind of `--no-admin` flag, if we just said, we +You may ask, why are we passing the `--no-admin` flag if we just said we want to set an admin to the contract? The answer is sad and confusing, but... -it is a different admin. The admin we want to set is one checked by the -contract itself and managed by him. The admin which is declined with -`--no-admin` flag, is a wasmd-level admin, which can migrate the contract. You -don't need to worry about the second one at least until you learn about -contract migrations - until then you can always pass the `--no-admin` flag to +it is a different admin. The admin we want to set is one checked and managed by the +contract itself. The admin which is declined with the +`--no-admin` flag is a wasmd-level admin, and is able to migrate the contract. You +don't need to worry about the latter at least until you learn about +contract migrations. Until then you can always pass the `--no-admin` flag to the contract. Now let's query our new contract for the member's list: @@ -292,7 +283,7 @@ data: ``` So, there is an admin, it seems like the one we wanted to have there. So now we -would add someone to the group - maybe ourselves? +can add someone to the group - maybe ourselves? ```bash wasmd tx wasm execute wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "update_members": { "add": [{ "addr": "wasm1um59mldkdj8ayl5gkn @@ -300,11 +291,11 @@ p9pnrdlw33v40sh5l4nx", "weight": 1 }], "remove": [] } }' --from wallet $TXFLAG - ``` The message for modifying the members is `update_members` and it has two -fields: members to remove, and members to add. Members to remove are -just addresses. Members to add have a bit more complex structure: they -are records with two fields: address and weight. Weight is not relevant +fields: one for members to `remove`, and another members to `add`. Members to remove are +just addresses. The structure of the `add` field has a slightly more complex structure: they +are records with two fields, address and weight. Weight is not relevant for us now, it is just metadata stored with every group member - for -us, it would always be 1. +us, it will always be 1. Let's query the contract again to check if our message changed anything: @@ -318,9 +309,9 @@ data: As you can see, the contract updated its state. This is basically how it works - sending messages to contracts causes them to update the state, -and the state can be queried at any time. For now, to keep things simple -we were just interacting with the contract directly by `wasmd`, but as described -before - contracts can communicate with each other. However, to investigate +and the state can be queried at any time. To keep things simple +we have until now just been interacting with the contract directly using `wasmd`, but as mentioned +before, contracts can communicate with each other. However, to investigate this we need to understand how to write contracts. Next time we will look -at the contract structure and we will map it part by part to what we have learned -until now. +at the contract structure and we will map it bit by bit to what we have learned +so far. diff --git a/src/actor-model/contract-as-actor.md b/src/actor-model/contract-as-actor.md index 2cb8b97..aaf6fee 100644 --- a/src/actor-model/contract-as-actor.md +++ b/src/actor-model/contract-as-actor.md @@ -1,17 +1,17 @@ # Smart contract as an actor -In previous chapters, we talked about the actor model and how it is implemented -in the blockchain. Now it is time to look closer into the typical contract -structure to understand how different features of the actor model are mapped to +In previous chapters we talked about the Actor Model and how it is implemented +on the blockchain. Now it is time to take a closer look at the typical contract +structure to understand how different features of the Actor Model map to it. -This will not be a step-by-step guide on contract creation, as it is a topic -for the series itself. It would be going through contract elements roughly to -visualize how to handle architecture in the actor model. +This will not be a step-by-step guide on contract creation, as that is a topic +for a series in itself. We shall instead go through various contract elements roughly to +visualize how to handle architecture in the Actor Model. ## The state -As before we would start with the state. Previously we were working with +As before we will start with the state. Previously we worked with the `cw4-group` contract, so let's start by looking at its code. Go to `cw-plus/contracts/cw4-group/src`. The folder structure should look like this: @@ -26,98 +26,89 @@ this: └──  state.rs ``` -As you may already figure out, we want to check the `state.rs` first. +As you may already have figured out, we want to check `state.rs` first. -The most important thing here is a couple of constants: `ADMIN`, `HOOKS`, -`TOTAL`, and `MEMBERS`. Every one of such constants represents a single portion -of the contract state - as tables in databases. The types of those constants -represent what kind of table this is. The most basic ones are `Item`, which +The most important thing to note here are is the use of a few constants: `ADMIN`, `HOOKS`, +`TOTAL`, and `MEMBERS`. Every one of these constants represents a single portion +of the contract state - just as tables in databases. The types of those constants +represent what kind of table it is. The most basic ones are of type `Item`, which keeps zero or one element of a given type, and `Map` which is a key-value map. -You can see `Item` is used to keep an admin and some other data: `HOOKS`, and +You can see that `Item` is used to keep an admin and some other data: `HOOKS`, and `TOTAL`. `HOOKS` is used by the `cw4-group` to allow subscription to any -changes to a group - a contract can be added as a hook, so when the group -changes, a message is sent to it. The `TOTAL` is just a sum of all members' +changes to a group. Thus a contract can be added as a hook, so that a message is sent to it whenever a group changes. The `TOTAL` is just a sum of all members' weights. -The `MEMBERS` in the group contract is the `SnapshotMap` - as you can imagine, -it is a `Map`, with some steroids - this particular one, gives us access to the -state of the map at some point in history, accessing it by the blockchain -`height`. `height` is the count of blocks created since the beggining of -blockchain, and it is the most atomic time representation in smart contracts. -There is a way to access the clock time in them, but everything happening in a -single block is considered happening in the same moment. +The `MEMBERS` in the group contract is of type `SnapshotMap` - as you can imagine, +it is a `Map` - but on steroids. This particular one gives us access to the +state of the map at any point in history, accessed by the blockchain +`height`. `height` is the count of blocks created since the beginning of +the blockchain itself, and it is the most atomic time representation available to smart contracts. +There is also a way to access the clock time in them, but everything that happens in a +single block is considered to have happened at the same moment. -Other types of storage objects not used in group contracts are: +Other types of storage objects that are not used in group contracts are: * `IndexedMap` - another map type, that allows accessing values by a variety of keys -* `IndexedSnapshotMap` - `IndexedMap` and `SnapshotMap` married +* `IndexedSnapshotMap` - `IndexedMap` and `SnapshotMap` married together -What is very important - every state type in the contract is accessed using -some name. All of those types are not containers, just accessors to the state. -Do you remember that I told you before that blockchain is our database? And -that is correct! All those types are just ORM to this database - when we use -them to get actual data from it, we pass a special `State` object to them, so -they can retrieve items from it. +It is very important to note that every state type in the contract is accessed using +some name. None of these types are containers, just accessors to the state. +Do you remember that we stated that the blockchain is our database? It's exactly so! +All these types are just [ORM](https://en.wikipedia.org/wiki/ORM) for this database - we use them by passing a special `State` object to them, enabling them to retrieve data items from the blockchain. -You may ask - why all that data for a contract are not auto-fetched by -whatever is running it. That is a good question. The reason is that we want +You may be wondering why all that contract data is not just auto-fetched by +whatever is running it. It's a good question. The answer is that we actually want our contracts to be lazy with fetching. Copying data is a very expensive operation, -and for everything happening on it, someone has to pay - it is realized by gas -cost. I told you before, that as a contract developer you don't need to worry -about gas at all, but it was only partially true. You don't need to know -exactly how gas is calculated, but by lowering your gas cost, you would may -execution of your contracts cheaper which is typically a good thing. One good -practice to achieve that is to avoid fetching data you will not use in a +and someone has to pay, as realized by the gas cost. It was mentioned before that as a contract developer you don't need to worry about gas at all. Well, that was only partially true. While you don't need to know +exactly how gas is calculated, lowering your contract's gas cost makes execution cheaper, which is a good thing. One good practice to aim for is simply to avoid fetching any data that you will not use in a particular call. ## Messages -In a blockchain, contracts communicate with each other by some JSON -messages. They are defined in most contracts in the `msg.rs` file. Take +On the blockchain, contracts communicate with each other via JSON +messages. They are defined in most contracts in the `msg.rs` file. Let's take a look at it. -There are three types on it, let's go through them one by one. -The first one is an `InstantiateMsg`. This is the one, that is sent -on contract instantiation. It typically contains some data which -is needed to properly initialize it. In most cases, it is just a +There are three types defined in it, so let's go through them one by one. +The first one is an `InstantiateMsg`. This message is sent +on contract instantiation and typically contains data that +is needed to properly initialize it. In most cases, this just has a simple structure. Then there are two enums: `ExecuteMsg`, and `QueryMsg`. They are -enums because every single variant of them represents a different +enums because every single one of their variants represents a different message which can be sent. For example, the `ExecuteMsg::UpdateAdmin` corresponds to the `update_admin` message we were sending previously. Note, that all the messages are attributed with `#[derive(Serialize, Deserialize)]`, and -`#[serde(rename_all="snake_case")]`. Those attributes come from +`#[serde(rename_all="snake_case")]`. These attributes come from the [serde](https://serde.rs/) crate, and they help us with -deserialization of them (and serialization in case of sending -them to other contracts). The second one is not required, -but it allows us to keep a camel-case style in our Rust code, -and yet still have JSONs encoded with a snake-case style more -typical to this format. +deserialization (and serialization in case of sending +messages to other contracts). The second one is not strictly required, +but it allows us to keep camel-case style in our Rust code while encoding the JSON in snake-case as is more +usual in this format. -I encourage you to take a closer look at the `serde` documentation, -like everything there, can be used with the messages. +You are encouraged to take a closer look at the `serde` documentation as everything there can be used with the messages. One important thing to notice - empty variants of those enums, -tend to use the empty brackets, like `Admin {}` instead of -more Rusty `Admin`. It is on purpose, to make JSONs cleaner, +tend to use empty brackets, e.g. `Admin {}` instead of +the more Rusty `Admin`. This is on purpose, to make the encoded JSON cleaner, and it is related to how `serde` serializes enum. -Also worth noting is that those message types are not set in stone, -they can be anything. This is just a convention, but sometimes -you would see things like `ExecuteCw4Msg`, or similar. Just keep -in mind, to keep your message name obvious in terms of their +Also worth noting is that these message types are not set in stone, in fact +we can use anything. This is just the convention, and you do sometimes +see things like `ExecuteCw4Msg` or similar. Just keep +in mind that it can be helpful to make sure your message names are obvious in terms of their purpose - sticking to `ExecuteMsg`/`QueryMsg` is generally a good idea. ## Entry points -So now, when we have our contract message, we need a way to handle +So now that we have our contract messages, we need a way to handle them. They are sent to our contract via entry points. There are three entry points in the `cw4-group` contract: @@ -147,178 +138,173 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { ``` Those functions are called by the CosmWasm virtual machine when -a message is to be handled by contract. You can think about them -as the `main` function of normal programs, except they have a signature -that better describes the blockchain itself. +a message is to be handled by the contract. You can think of them +as something like the `main` function of normal programs, except they have signatures +that better suit interction with the blockchain. -What is very important is that the names of those entry points (similarly to -the `main` function) are fixed - it is relevant, so the virtual machine knows +It is very important that the names of these entry points are fixed (similarly to +the `main` function). This ensures the virtual machine knows exactly what to call. So, let's start with the first line. Every entry point is attributed with `#[cfg_attr(not(feature = "library"), entry_point)]`. It may look a bit -scary, but it is just a conditional equivalent of `#[entry_point]` - -the attribute would be there if and only if the "library" feature is not set. -We do this to be able to use our contracts as dependencies for other -contracts - the final binary can contain only one copy of each entry point, -so we make sure, that only the top-level one is compiled without this -feature. +scary, but it is just a conditional version of `#[entry_point]` - +the attribute is applied if and only if the "library" feature is not set. +We do this so that we are able to use our contracts as dependencies for other +contracts. Since the final binary can contain only one copy of each entry point, +this makes sure that only the top-level one is compiled with this +feature (entry point). The `entry_point` attribute is a macro that generates some boilerplate. -As the binary is run by WASM virtual machine, it doesn't know much about -Rust types - the actual entry point signatures are very inconvenient to -use. To overcome this issue, there is a macro created, which generates -entry points for us, and those entry points are just calling our functions. +As the binary is run by WASM virtual machine, it doesn't actually know much about +Rust types. The actual entry point signatures are very inconvenient to +use. To overcome this issue this macro is used to generate the appropriate +entry points that will call our functions for us. -Now take a look at functions arguments. Every single entry point takes as -the last argument a message which triggered the execution of it (except for -`reply` - I will explain it later). In addition to that, there are -additional arguments provided by blockchain: +Now take a look at the arguments of the functions. Every single entry point takes as +the last argument the message which triggered its execution (except for +`reply` which will be explained later). In addition to this, the following arguments are provided by the blockchain: * `Deps` or `DepsMut` object is the gateway to the world outside the smart contract context. It allows - accessing the contract state, as well as querying other contracts, and + access to the contract state, as well as querying other contracts. It also delivers an `Api` object with a couple of useful utility functions. - The difference is that `DepsMut` allows updating state, while `Deps` - allows only to look at it. + The difference between them is that `DepsMut` allows the state to be updated, while `Deps` + only allows it to be looked at. * `Env` object delivers information about the blockchain state at the moment of execution - its height, the timestamp of execution and information about the executing contract itself. * `MessageInfo` object is information about the contract call - it - contains the address which sends the message, and the funds sent with the + contains the address of the sender of the message and any funds sent with the message. -Keep in mind, that the signatures of those functions are fixed (except -the messages type), so you cannot interchange `Deps` with `DepsMut` to -update the contract state in the query call. +Keep in mind that the signatures of these functions are fixed (except for +the messages type), so for example you cannot interchange `Deps` with `DepsMut` to +update the contract state in the query call! -The last portion of entry points is the return type. Every entry point returns -a `Result` type, with any error which can be turned into a string - in case of +The last element of the entry points is the return type. Every entry point returns +a `Result` type which includes any error that can be turned into a string. In case of contract failure, the returned error is just logged. In most cases, the error -type is defined for a contract itself, typically using a +type is defined for the contract itself, typically using the [thiserror](https://docs.rs/thiserror/latest/thiserror/) crate. `Thiserror` is -not required here, but is strongly recommended - using it makes the error -definition very straightforward, and also improves the testability of the +not strictly required here, but is strongly recommended - using it makes error +definitions very straightforward and improves the testability of the contract. -The important thing is the `Ok` part of `Result`. Let's start with the +Its important to understand the `Ok` part of `Result`. Let's start with the `query` because this one is the simplest. The query always returns the `Binary` -object on the `Ok` case, which would contain just serialized response. -The common way to create it is just calling a `to_binary` method -on an object implementing `serde::Serialize`, and they are typically +object on the `Ok` case, which contains just a serialized response. +The most common way to create it is just to call the `to_binary` method +on an object implementing `serde::Serialize`. Responses are typically defined in `msg.rs` next to message types. -Slightly more complex is the return type returned by any other entry -point - the `cosmwasm_std::Response` type. This one keep everything -needed to complete contract execution. There are three chunks of -information in that. +Slightly more complicated is the return type returned by the other entry +points - the `cosmwasm_std::Response` type. This one stores everything that is +needed to complete contract execution. This consists of three chunks of +information. -The first one is an `events` field. It contains all events, which would +The first one is an `events` field. It contains all events that will be emitted to the blockchain as a result of the execution. Events have -a really simple structure: they have a type, which is just a string, +a really simple structure: they have a type which is just a string, and a list of attributes which are just string-string key-value pairs. -You can notice that there is another `attributes` field on the `Response`. -This is just for convenience - most executions would return -only a single event, and to make it a bit easier to operate one, there -is a set of attributes directly on response. All of them would be converted -to a single `wasm` event which would be emitted. Because of that, I consider +You may notice that there is another `attributes` field on the `Response`. +This is just for convenience since most executions will return +only a single event: to make it a bit easier to operate one, a set of attributes are defined directly on the response. All of them will be converted +to a single `wasm` event which will be emitted. For this reason, we can consider `events` and `attributes` to be the same chunk of data. -Then we have the messages field, of `SubMsg` type. This one is the clue -of cross-contact communication. Those messages would be sent to the -contracts after processing. What is important - the whole execution is -not finished, unless the processing of all sub-messages scheduled by the contract -finishes. So, if the group contract sends some messages as a result of +Finally we have the messages field, of `SubMsg` type. This one is the glue +of cross-contract communication. These messages will be sent to the +contracts after processing. It is important to remember that the whole execution is +not considered complete until or unless the processing of all sub-messages scheduled by the contract +completes. So, if the group contract sends some messages as a result of `update_members` execution, the execution would be considered done only if -all the messages sent by it would also be handled (even if they failed). +all the messages sent by it are handled (even if this handling results in failure). -So, when all the sub-messages sent by contract are processed, then all the +When all the sub-messages sent by the contract are processed, then all the attributes generated by all sub-calls and top-level calls are collected and -reported to the blockchain. But there is one additional piece of information - -the `data`. So, this is another `Binary` field, just like the result of a query -call, and just like it, it typically contains serialized JSON. Every contract -call can return some additional information in any format. You may ask - in -this case, why do we even bother returning attributes? It is because of a +reported to the blockchain. There is one additional piece of information to consider- +the `data`. This is another `Binary` field, just like the result of a query +call and similarly it typically contains serialized JSON. Every contract +call can return some additional information in any format. You may be wondering why do we even bother returning attributes if this is the case? It is because they form a completely different way of emitting events and data. Any attributes emitted by -the contract would be visible on blockchain eventually (unless the whole -message handling fails). So, if your contract emitted some event as a result of -being sub-call of some bigger use case, the event would always be there visible -to everyone. This is not true for data. Every contract call would return only -a single `data` chunk, and it has to decide if it would just forward the `data` -field of one of the sub-calls, or maybe it would construct something by itself. -I would explain it in a bit more detail in a while. +the contract will be visible on the blockchain eventually unless the whole +message handling fails. So, if your contract emitted some event as a result of +being a sub-call of some bigger use case, the event will always be there visible +to everyone. This is not true for data. Every contract call will return only +a single `data` chunk, and it has to decide if it will just forward the `data` +field of one of the sub-calls "as is", or if it will construct something for itself. +This will be explained in more detail later. ## Sending submessages -I don't want to go into details of the `Response` API, as it can be read -directly from documentation, but I want to take a bit closer look at the part +We won't go into too many details on the `Response` API, since these can be read +directly from the documentation, but let's take a bit of a closer look at the part about sending messages. The first function to use here is `add_message`, which takes as an argument the -`CosmosMsg` (or rather anything convertible to it). A message added to response -this way would be sent and processed, and its execution would not affect the +`CosmosMsg` (or rather anything that can be converted to it). A message added to a response +in this way will be sent and processed, and its execution will not affect the result of the contract at all. -The other function to use is `add_submessage`, taking a `SubMsg` argument. It -doesn't differ much from `add_message` - `SubMsg` just wraps the `CosmosMsg`, -adding some info to it: the `id` field, and `reply_on`. There is also a -`gas_limit` thing, but it is not so important - it just causes sub-message -processing to fail early if the gas threshold is reached. +The other function to use is `add_submessage`, taking a `SubMsg` argument. This +doesn't differ much from `add_message` since `SubMsg` just wraps the `CosmosMsg` while +adding some more info to it: the `id` field, and `reply_on`. There is also a +`gas_limit` property, but it is not so important (it causes sub-message +processing to fail early if the gas threshold is reached). -The simple thing is `reply_on` - it describes if the `reply` message should be +`reply_on` describes whether the `reply` message should be sent on processing success, on failure, or both. -The `id` field is an equivalent of the order id in our KFC example from the -very beginning. If you send multiple different sub-messages, it would be -impossible to distinguish them without that field. It would not even be -possible to figure out what type of original message reply handling is! This is -why the `id` field is there - sending a sub-message you can set it to any -value, and then on the reply, you can figure out what is happening based on -this field. - -An important note here - you don't need to worry about some sophisticated way -of generating ids. Remember, that the whole processing is atomic, and only one -execution can be in progress at once. In most cases, your contract sends a -fixed number of sub-messages on very concrete executions. Because of that, you +The `id` field is an equivalent of the "order id" in our KFC example from before. Without this field, if we sent multiple different sub-messages, it would be +impossible to distinguish between them. It would not even be +possible to figure out what the type of the original message handling the reply was! This is +why the `id` field exists - it can be set to any value when sending a sub-message and then on receiving the reply you can figure out what is happening based on +it. + +Another important note here - you don't need to worry about setting up some sophisticated way +of generating ids. Remember that the whole process is atomic and only one +execution can be in progress at a time. In most cases, your contract sends a +fixed number of sub-messages on very concrete executions. This means that in practice you can hardcode most of those ids while sending (preferably using some constant). -To easily create submessages, instead of setting all the fields separately, -you would typically use helper constructors: `SubMsg::reply_on_success`, +To easily create submessages, instead of setting all the fields separately +you would typically use the following helper constructors: `SubMsg::reply_on_success`, `SubMsg::reply_on_error` and `SubMsg::reply_always`. ## CosmosMsg -If you took a look at the `CosmosMsg` type, you could be very surprised - there -are so many variants of them, and it is not obvious how they relate to +If you take a look at the `CosmosMsg` type you may be very surprised - there +are so many variants, and it is not obvious how they relate to communication with other contracts. The message you are looking for is the `WasmMsg` (`CosmosMsg::Wasm` variant). -This one is very much similar to what we already know - it has a couple of -variants of operation to be performed by contracts: `Execute`, but also -`Instantiate` (so we can create new contracts in contract executions), and also -`Migrate`, `UpdateAdmin`, and `ClearAdmin` - those are used to manage -migrations (will tell a bit about them at the end of this chapter). +This one is very similar to what we already know. It has a few +variants for operations to be performed by contracts: `Execute`, and +`Instantiate` (so we can create new contracts in contract executions). It also has +`Migrate`, `UpdateAdmin`, and `ClearAdmin` variants which are used to manage +migrations, which we'll discuss more at the end of this chapter. Another interesting message is the `BankMsg` (`CosmosMsg::Bank`). This one allows a contract to transfer native tokens to other contracts (or burn them - -equivalent to transferring them to some black whole contract). I like to think -about it as sending a message to a very special contract responsible for handling -native tokens - this is not a true contract, as it is handled by the blockchain -itself, but at least to me it simplifies things. +equivalent to transferring them to some black hole contract). You may like to think +about this as being like sending a message to a very special contract responsible for handling +native tokens. Of course, in reality this is not a real contract, as it is in fact handled by the blockchain +itself, but this may be a way of simplifying things conceptually. -Other variants of `CosmosMsg` are not very interesting for now. The `Custom` +Other variants of `CosmosMsg` are not of much interest to us for now. The `Custom` one is there to allow other CosmWasm-based blockchains to add some -blockchain-handled variant of the message. This is a reason why most -message-related types in CosmWasm are generic over some `T` - this is just a -blockchain-specific type of message. We will never use it in the `wasmd`. All -other messages are related to advanced CosmWasm features, and I will not +blockchain-handled variant of the message. This is why most +message-related types in CosmWasm are generic over some `T` (to allow a +blockchain-specific type of message). We will never use it in the `wasmd`. All +other messages are related to advanced CosmWasm features and we shall not describe them here. ## Reply handling -So now that we know how to send a submessage, it is time to talk about -handling the reply. When sub-message processing is finished, and it is -requested to reply, the contract is called with an entry point: +So now that we know how to send a sub-message, it is time to talk about +handling the reply. When sub-message processing is finished, and a +reply is requested, the contract is called with the following entry point: ```rust #[cfg_attr(not(feature = "library"), entry_point)] @@ -328,56 +314,52 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result` type, except it is there for -serialization purposes. You can easily convert it into a `Result` with an +is an `id` - the same one that you set when sending sub-message, so now you can +identify the response. The other one is the `ContractResult`, which is very +similar to the Rust `Result` type, except that it exists for +serialization purposes. You can easily convert it into a `Result` by using the `into_result` function. -In the error case of `ContracResult`, there is a string - as I mentioned +In the error case of `ContractResult`, we get a string. As mentioned before, errors are converted to strings right after execution. The `Ok` case contains `SubMsgExecutionResponse` with two fields: `events` emitted by -sub-call, and the `data` field embedded on response. - -As said before, you never need to worry about forwarding events - CosmWasm -would do it anyway. The `data` however, is another story. As mentioned before, -every call would return only a single data object. In the case of sending -sub-messages and not capturing a reply, it would always be whatever is returned -by the top-level message. But it is not the case when `reply` is called. If a -a reply is called, then it is a function deciding about the final `data`. It can +the sub-call, and the `data` field embedded in the response. + +Also mentioned before, you never need to worry about forwarding events - CosmWasm +does it automatically anyway. The `data` is another story, however. Every call will return only a single data object. When sending +sub-messages and not capturing a reply, this will always be whatever is returned +by the top-level message. However, when `reply` is called this does not have to be the case. If a +a reply is called, then it is a function which makes a decision about the final `data`. It can decide to either forward the data from the sub-message (by returning `None`) or -to overwrite it. It cannot choose, to return data from the original execution -processing - if the contract sends sub-messages waiting for replies, it is -supposed to not return any data, unless replies are called. +to overwrite it. What it cannot do is choose to return data from the original execution +processing. If the contract sends sub-messages waiting for replies, it is not +supposed to return any data, unless and until replies are called. But what happens if multiple sub-messages are sent? What would the final -`data` contain? The rule is - the last non-None. All sub-messages are always -called in the order of adding them to the `Response`. As the order is -deterministic and well defined, it is always easy to predict which reply would +`data` contain? The rule is - the last non-None one. All sub-messages are always +called in the same order as they are added to the `Response`. As the order is +deterministic and well defined, it is always easy to predict which reply will be used. ## Migrations -I mentioned migrations earlier when describing the `WasmMsg`. So, migration -is another action possible to be performed by contracts, which is kind -of similar to instantiate. In software engineering, it is a common thing to -release an updated version of applications. It is also a case in the blockchain - -SmartContract can be updated with some new features. In such cases, a new -code is uploaded, and the contract is migrated - so it knows that from -this point, its messages are handled by another, updated contract code. +Migrations were mentioned earlier when describing `WasmMsg`. Migration +is another action that contracts can perform, and is somewhat similar to instantiate. +In software engineering, it is common to release updated versions of applications. This is also true in the case of blockchain - smart contracts can be updated with new features. In such cases, new +code is uploaded and the contract is migrated so that it knows that from +this point on, its messages are to be handled by another (updated) contract code. However, it may be that the contract state used by the older version of the -contract differs from the new one. It is not a problem if some info was -added (for example some additional map - it would be just empty right -after migration). But the problem is, when the state changes, -for example, the field is renamed. In such a case, every contract execution -would fail because of (de)serialization problems. Or even more subtle -cases, like adding a map, but one which should be synchronized with the whole -contract state, not empty. +contract differs from the new one. This is not a problem if some info was +added (for example some additional map - this would just be empty right +after migration). Complications arise when the state actually changes, +for example if a field is renamed. In this case, every contract execution +would fail because of (de)serialization problems. Even more subtle +cases can cause problems, such as adding a map which isn't empty but in fact needs to be synchronised with the whole contract state. This is the purpose of the `migration` entry point. It looks like this: @@ -389,30 +371,27 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result, ``` `MigrateMsg` is the type defined by the contract in `msg.rs`. -The `migrate` entry point would be called at the moment of performing +The `migrate` entry point will be called at the moment of performing the migration, and it is responsible for making sure the state is correct after the migration. It is very similar to schema migrations in traditional -database applications. And it is also kind of difficult, because of version -management involved - you can never assume, that you are migrating a contract -from the previous version - it can be migrated from any version, released -anytime - even later than that version we are migrating to! +database applications. It is also rather challenging, because of the complicated version +management involved. You can never assume that you are migrating a contract +from the previous version! It can be migrated from any version, released +anytime - even later than the version we are migrating to! It is worth bringing back one issue from the past - the contract admin. Do you remember the `--no-admin` flag we set previously on every contract instantiation? It made our contract unmigrateable. Migrations can be performed -only by contract admin. To be able to use it, you should pass `--admin address` -flag instead, with the `address` being the address that would be able to +only by the contract admin. To be able to use it, you should pass a `--admin address` +flag instead, with the `address` being the address that will be able to perform migrations. ## Sudo -Sudo is the last basic entry point in `CosmWasm`, and it is the one we would -never use in `wasmd`. It is equivalent to `CosmosMsg::Custom`, but instead of -being a special blockchain-specific message to be sent and handled by a +Sudo is the last basic entry point in `CosmWasm`, and it is one we won't use in `wasmd`. It is equivalent to `CosmosMsg::Custom`, but instead of +being a special blockchain-specific message sent and handled by the blockchain itself, it is now a special blockchain-specific message sent by the -blockchain to contract in some conditions. There are many uses for those, but I -will not cover them, because would not be related to `CosmWasm` itself. The -signature of `sudo` looks like this: +blockchain to the contract in some conditions. There are many uses cases, but since they are not related to `CosmWasm` we will not cover them here. The signature of `sudo` looks like this: ```rust #[cfg_attr(not(feature = "library"), entry_point)] @@ -421,6 +400,6 @@ pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/type.StdResult.html) for this simple example, -which is an alias for `Result`. The return entry point type would always be a -[`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) type, with some error type implementing -[`ToString`](https://doc.rust-lang.org/std/string/trait.ToString.html) trait and a well-defined type for success +which is an alias for `Result`. The return entry point type should always be a +[`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) type, with some error type implementing the +[`ToString`](https://doc.rust-lang.org/std/string/trait.ToString.html) trait and a well-defined type for the success case. For most entry points, an "Ok" case would be the -[`Response`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html) type that allows fitting the contract -into our actor model, which we will discuss very soon. +[`Response`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html) that allows the contract to fit into our "Actor Model", which we shall discuss very soon. -The body of the entry point is as simple as it could be - it always succeeds with a trivial empty response. +The body of the entry point is as simple as it can be - it always succeeds with a trivial empty response. diff --git a/src/basics/events.md b/src/basics/events.md index 9fef0be..7b3f60e 100644 --- a/src/basics/events.md +++ b/src/basics/events.md @@ -1,17 +1,17 @@ # Events attributes and data -The only way our contract can communicate to the world, for now, is by queries. +The only way our contract can communicate with the outside world, for now, is by means of queries. Smart contracts are passive - they cannot invoke any action by themselves. They -can do it only as a reaction to a call. But if you tried playing with `wasmd`, -you know that execution on the blockchain can return some metadata. +can do so only in response to a call. However, if you have ever tried playing around with `wasmd`, +you'll know that execution on the blockchain can return some metadata. -There are two things the contract can return to the caller: events and data. +There are two things the contract can return to the caller: `event`s and `data`. Events are something produced by almost every real-life smart contract. In -contrast, data is rarely used, designed for contract-to-contract communication. +contrast, `data` is designed for contract-to-contract communication and is much more rarely used, and it . ## Returning events -As an example, we would add an event `admin_added` emitted by our contract on the execution of +As an example, we shall add an event `admin_added`, which shall be emitted by our contract on the execution of `AddMembers`: ```rust,noplayground @@ -260,18 +260,16 @@ An event is built from two things: an event type provided in the Attributes are added to an event with the [`add_attributes`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Event.html#method.add_attributes) or the [`add_attribute`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Event.html#method.add_attribute) -call. Attributes are key-value pairs. Because an event cannot contain any list, to achieve reporting -multiple similar actions taking place, we need to emit multiple small events instead of a collective one. +call. Attributes are key-value pairs. Because an event cannot contain a list, to achieve the reporting +of multiple similar actions taking place, we need to emit multiple small events rather than a single collective one. Events are emitted by adding them to the response with [`add_event`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html#method.add_event) or [`add_events`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html#method.add_events) call. -Additionally, there is a possibility to add attributes directly to the response. It is just sugar. By default, +Additionally, it is possible to add attributes directly to the response. However, this is just sugar. By default, every execution emits a standard "wasm" event. Adding attributes to the result adds them to the default event. -We can check if events are properly emitted by contract. It is not always done, as it is much of boilerplate in -test, but events are, generally, more like logs - not necessarily considered main contract logic. Let's now write -single test checking if execution emits events: +We can check if events are properly emitted by our contract. It's not always done, since there is a lot of boilerplate in the test and events are generally more like logs so not necessarily considered main contract logic. Let's now write a single test checking if execution emits events: ```rust,noplayground # use crate::error::ContractError; @@ -582,22 +580,20 @@ mod tests { } ``` -As you can see, testing events on a simple test made it clunky. First of all, -every string is heavily string-based - a lack of type control makes writing -such tests difficult. Also, even types are prefixed with "wasm-" - it may not -be a huge problem, but it doesn't clarify verification. But the problem is, how -layered events structure are, which makes verifying them tricky. Also, the -"wasm" event is particularly tricky, as it contains an implied attribute - -`_contract_addr` containing an address called a contract. My general rule is - -do not test emitted events unless some logic depends on them. +As you can see, testing events even in a simple test is clunky. First of all, +every check is heavily string-based - a lack of type control makes writing +such tests difficult. Also, event types are prefixed with "wasm-" - this may not +be a huge problem, but it certainly doesn't help clarify verification. However, the main problem lies in the layered events structure. This makes verifying them particularly tricky. Further, the +"wasm" event itself is also tricky, as it contains an implied attribute - +`_contract_addr` containing an address called a contract. Given these difficulties, the author follows a general approach of not testing emitted events unless some logic depends on them! ## Data Besides events, any smart contract execution may produce a `data` object. In contrast to events, `data` -can be structured. It makes it a way better choice to perform any communication logic relies on. On the -other hand, it turns out it is very rarely helpful outside of contract-to-contract communication. Data -is always only one single object on the response, which is set using the +can be structured. This makes it a much better choice for performing any communication that the contract logic relies upon. On the +other hand, it turns out that it is very rarely helpful outside of contract-to-contract communication. Data +is always only one single object in the response, which is set using the [`set_data`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html#method.set_data) function. -Because of its low usefulness in a single contract environment, we will not spend time on it right now - an +Because it is so rarely useful outside of a single contract environment, we will not spend time on it right now - an example of it will be covered later when contract-to-contract communication will be discussed. Until then, -it is just helpful to know such an entity exists. +it is just helpful to bear in mind that such an entity exists. diff --git a/src/basics/execute.md b/src/basics/execute.md index 65920f6..424ab79 100644 --- a/src/basics/execute.md +++ b/src/basics/execute.md @@ -1,16 +1,14 @@ # Execution messages -We went through instantiate and query messages. It is finally time to introduce the last basic entry point - -the execute messages. It is similar to what we have done so far that I expect this to be just chilling and -revisiting our knowledge. I encourage you to try implementing what I am describing here on your own as an -exercise - without checking out the source code. +We have been through instantiate and query messages. It is finally time to introduce the last basic entry point - +the execute message. It is similar to what we have done so far, so you can expect this to be quite easy as we are just revisiting our knowledge. We encourage you to try implementing what is described on your own first before checking the source code as an exercise . -The idea of the contract will be easy - every contract admin would be eligible to call two execute messages: +The idea of the contract is simple: every contract admin is eligible to call the following two execute messages: -* `AddMembers` message would allow the admin to add another address to the admin's list -* `Leave` would allow and admin to remove himself from the list +* `AddMembers` admins can call this message to add another address to the admin's list +* `Leave` admins can call this message to remove themselves from the admin list -Not too complicated. Let's go coding. Start with defining messages: +That doesn't sound too complicated, so let's get coding! We'll start with defining the messages: ```rust,noplayground # use cosmwasm_std::Addr; @@ -44,7 +42,7 @@ pub enum ExecuteMsg { # } ``` -And implement entry point: +Next, we implement the entry point: ```rust,noplayground use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; @@ -241,7 +239,7 @@ mod exec { # } ``` -The entry point itself also has to be created in `src/lib.rs`: +The real entry point itself also has to be created in `src/lib.rs`: ```rust,noplayground use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; @@ -272,36 +270,30 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } ``` -There are a couple of new things, but nothing significant. First is how do I reach the message sender -to verify he is an admin or remove him from the list - I used the `info.sender` field of `MessageInfo`, -which is how it looks like - the member. As the message is always sent from the proper address, the -`sender` is already of the `Addr` type - no need to validate it. Another new thing is the +There are a couple of new things here, but nothing major. The first is how we handled getting the identity of the message sender is so that we could verify that they are actually allowed to carry out the execute functions. We used the `info.sender` field of `MessageInfo`, which is exactly as it sounds like. As the message is always sent from a proper address, the +`sender` is already of the `Addr` type and there is no need to validate it. The other new thing is the [`update`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.update) -function on an `Item` - it makes a read and update of an entity potentially more efficient. It is -possible to do it by reading admins first, then updating and storing the result. +function on an `Item` - it makes the read and update of an entity potentially more efficient. This would also be possible by reading the admins first, followed by updating and storing the result. -You probably noticed that when working with `Item`, we always assume something -is there. But nothing forces us to initialize the `ADMINS` value on -instantiation! So what happens there? Well, both `load` and `update` functions -would return an error. But there is a +You may have noticed that when working with `Item`, we have been always assuming that something +is actually there. However, this may not be warranted, since there's nothing forcing us to initialize the `ADMINS` value on +instantiation! So what would happen in then? Well, both `load` and `update` functions +would return an error. However, there is also the option of using the [`may_load`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.may_load) -function, which returns `StdResult>` - it would return `Ok(None)` in -case of empty storage. There is even a possibility to remove an existing item -from storage with the +function, which returns `StdResult>`. In the case of empty storage, it returns `Ok(None)`. It is also possible to remove an existing item from storage with the [`remove`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.remove) function. -One thing to improve is error handling. While validating the sender to be admin, we are returning -some arbitrary string as an error. We can do better. +One thing we need to improve is the error handling. While validating that the sender is an admin, we are just returning +some arbitrary string as an error. We can do better! ## Error handling -In our contract, we now have an error situation when a user tries to execute `AddMembers` not being -an admin himself. There is no proper error case in +In our contract, we now generate an error whenever a non-admin user tries to execute `AddMembers`. There is no proper error case in [`StdError`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/enum.StdError.html) to report this -situation, so we have to return a generic error with a message. It is not the best approach. +situation, so we have resorted to just returning a generic error with a message, which is not the best approach. -For error reporting, we encourage using [`thiserror`](https://crates.io/crates/thiserror/1.0.24/dependencies) +For error reporting, we recommend using the [`thiserror`](https://crates.io/crates/thiserror/1.0.24/dependencies) crate. Start with updating your dependencies: ```toml @@ -370,17 +362,17 @@ mod state; # } ``` -Using `thiserror` we define errors like a simple enum, and the crate ensures -that the type implements +Using `thiserror` we define errors just in a simple enum, and the crate ensures +that the type implements the [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html) -trait. A very nice feature of this crate is the inline definition of -[`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait by an -`#[error]` attribute. Also, another helpful thing is the `#[from]` attribute, -which automatically generates proper +trait. A very nice feature of this crate is the inline definition of the +[`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait by use of an +`#[error]` attribute. Another helpful thing is the `#[from]` attribute, +which automatically generates a proper [`From`](https://doc.rust-lang.org/std/convert/trait.From.html) implementation, -so it is easy to use `?` operator with `thiserror` types. +allowing us to use the `?` operator with `thiserror` types. -Now update the execute endpoint to use our new error type: +Now we can update the execute endpoint to use our new error type: ```rust,noplayground use crate::error::ContractError; @@ -617,11 +609,11 @@ pub fn execute( ## Custom error and multi-test -Using proper custom error type has one nice upside - multi-test is maintaining error type using -the [`anyhow`](https://crates.io/crates/anyhow) crate. It is a sibling of `thiserror`, designed -to implement type-erased errors in a way that allows getting the original error back. +Using a proper custom error type has one more nice advantage - multi-test maintains the error type using +the [`anyhow`](https://crates.io/crates/anyhow) crate. This is a sibling of `thiserror`, designed +to implement type-erased errors in a way that allows us to get the original error back. -Let's write a test that verifies that a non-admin cannot add himself to a list: +Let's write a test that verifies that a non-admin cannot add themselves to the list: ```rust,noplayground # use crate::error::ContractError; @@ -858,23 +850,21 @@ mod tests { Executing a contract is very similar to any other call - we use an [`execute_contract`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/trait.Executor.html#method.execute_contract) function. As the execution may fail, we get an error type out of this call, but -instead of calling `unwrap` to extract a value out of it, we expect an error to -occur - this is the purpose of the +instead of calling `unwrap` to extract a value out of it, we make use of the [`unwrap_err`](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err) -call. Now, as we have an error value, we can check if it matches what we -expected with an `assert_eq!`. There is a slight complication - the error +call since we are expecting an error to +occur. Now that we have an error value, we can check whether it matches what we +expected with an `assert_eq!`. There is a slight complication here, however: the error returned from `execute_contract` is an [`anyhow::Error`](https://docs.rs/anyhow/1.0.57/anyhow/struct.Error.html) -error, but we expect it to be a `ContractError`. Hopefully, as I said before, +error, but we are expecting a `ContractError`. Thankfully, as mentioned above, `anyhow` errors can recover their original type using the [`downcast`](https://docs.rs/anyhow/1.0.57/anyhow/struct.Error.html#method.downcast) -function. The `unwrap` right after it is needed because downcasting may fail. -The reason is that `downcast` doesn't magically know the type kept in the -underlying error. It deduces it by some context - here, it knows we expect it -to be a `ContractError`, because of being compared to it - type elision -miracles. But if the underlying error would not be a `ContractError`, then +function. The `unwrap` right after it is required because downcasting may fail, because `downcast` doesn't magically know the type kept in the +underlying error. It deduces this by using the context - in this case, it knows we expect it +to be a `ContractError` by the fact that it is being compared to it (type elision +miracles!). If the underlying error was not a `ContractError`, then `unwrap` would panic. -We just created a simple failure test for execution, but it is not enough to claim the contract is production-ready. -All reasonable ok-cases should be covered for that. I encourage you to create some tests and experiment with them as -an exercise after this chapter. +We have just created a simple failure test for execution, but it is not enough by itself to enable us to claim that the contract is production-ready. +We would need to cover all reasonable Ok-cases to be able to claim that. We encourage you to create some tests and experiment with them as an exercise after this chapter. diff --git a/src/basics/fp-types.md b/src/basics/fp-types.md index 1ec1bbc..d91d7b6 100644 --- a/src/basics/fp-types.md +++ b/src/basics/fp-types.md @@ -1,24 +1,21 @@ # Floating point types -Now you are ready to create smart contracts on your own. It is time to discuss an important limitation of CosmWasm -smart contracts - floating-point numbers. +Now that you are ready to create smart contracts on your own, it is time to discuss an important limitation of CosmWasm smart contracts: floating-point numbers. -The story is short: you cannot use floating-point types in smart contracts. Never. CosmWasm virtual machine on purpose -does not implement floating-point Wasm instructions, even such basics as `F32Load`. The reasoning is simple: they are -not safe to work with in the blockchain world. +The story is short: you cannot use floating-point types in smart contracts. Never. CosmWasm virtual machine deliberately does not implement floating-point Wasm instructions, even such basics as `F32Load`. The reason is simple: they are not safe to work with in the blockchain world. -The biggest problem is that contract will compile, but uploading it to the blockchain would fail with an error message claiming there is a floating-point operation in the contract. A tool that verifies if the contract is valid (it does not contain any fp operations but also has all needed entry points and so on) is called `cosmwasm-check` [utility](https://github.com/CosmWasm/cosmwasm/tree/main/packages/check). +One serious issue is that a contract may compile, but uploading it to the blockchain could still fail with an error message stating that there is a floating-point operation in the contract. A tool that verifies that the contract is valid (i.e. does not contain any floating-point operations and also has all of the required entry points and so on) is called `cosmwasm-check` [utility](https://github.com/CosmWasm/cosmwasm/tree/main/packages/check). -This limitation has two implications. First, you always have to use decimal of fixed-point arithmetic in your contracts. -It is not a problem, considering that `cosmwasm-std` provides you with the +This limitation has two implications. First, you always have to use decimal or fixed-point arithmetic in your contracts. +This is not really a problem, considering that `cosmwasm-std` provides you with the [`Decimal`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Decimal.html) and [Decimal256](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Decimal256.html) types. -The other implication is tricky - you must be careful with the crates you use. In particular, one gotcha in the `serde` -crate - deserialization of `usize` type is using floating-point operations. That means you can never use `usize` (or `isize`) +The other implication is trickier - you must be careful with the crates you use. In particular, one gotcha occurs in the `serde` +crate - deserialization of the `usize` type uses floating-point operations! That means you can never use `usize` (or `isize`) types in your deserialized messages in the contract. -Another thing that will not work with serde is untagged enums deserialization. The workaround is to create custom -deserialization of such enums using [`serde-cw-value`](https://crates.io/crates/serde-cw-value) crate. It is a fork of -[`serde-value`](https://crates.io/crates/serde-value) crate which avoids generating floating-point instructions. +Another thing that will not work with serde is the deserialization of untagged enums. The workaround is to create custom +deserialization of such enums using [`serde-cw-value`](https://crates.io/crates/serde-cw-value) crate. It is a fork of the +[`serde-value`](https://crates.io/crates/serde-value) crate that avoids generating floating-point instructions. diff --git a/src/basics/funds.md b/src/basics/funds.md index 223d42e..3f15ce9 100644 --- a/src/basics/funds.md +++ b/src/basics/funds.md @@ -1,28 +1,25 @@ # Dealing with funds When you hear smart contracts, you think blockchain. When you hear blockchain, -you often think of cryptocurrencies. It is not the same, but crypto assets, or -as we often call them: tokens, are very closely connected to the blockchain. -CosmWasm has a notion of a native token. Native tokens are assets managed by -the blockchain core instead of smart contracts. Often such assets have some -special meaning, like being used for paying [gas +you often think of cryptocurrencies. Although they are not exactly the same thing, crypto assets (or +`tokens` as they are often called) are indeed very closely connected to the blockchain. +CosmWasm has the notion of a native token. Native tokens are assets managed by +the blockchain core instead of by smart contracts. Often such assets have some +special use-case, such as being used for paying [gas fees](https://docs.cosmos.network/master/basics/gas-fees.html) or -[staking](https://en.wikipedia.org/wiki/Proof_of_stake) for consensus -algorithm, but can be just arbitrary assets. +[staking](https://en.wikipedia.org/wiki/Proof_of_stake) for a consensus +algorithm, but they can also be just any arbitrary assets. -Native tokens are assigned to their owners but can be transferred by their -nature. Everything had an address in the blockchain is eligible to have its -native tokens. As a consequence - tokens can be assigned to smart contracts! -Every message sent to the smart contract can have some funds sent with it. In -this chapter, we will take advantage of that and create a way to reward hard -work performed by admins. We will create a new message - `Donate`, which will be -used by anyone to donate some funds to admins, divided equally. +Native tokens are assigned to their owners but can also be transferred. Everything that has an address on the blockchain is eligible to have (own) native tokens. As a consequence - tokens can also be assigned to smart contracts! +Every message sent to the smart contract can have some funds sent along with it. In +this chapter, we will take advantage of this and create a way to reward hard +work performed by admins. We will create a new message - `Donate`, which can be +used by anyone to donate some funds to admins, which will be divided equally between them. ## Preparing messages -Traditionally we need to prepare our messages. We need to create a new -`ExecuteMsg` variant, but we will also modify the `Instantiate` message a bit - -we need to have some way of defining the name of a native token we would use +As before, we need to prepare our messages. We need to create a new +`ExecuteMsg` variant, but we will also modify the `Instantiate` message a bit as we need some way of defining the name of the native token we'll use for donations. It would be possible to allow users to send any tokens they want, but we want to simplify things for now. @@ -382,8 +379,9 @@ pub fn instantiate( # } ``` -What also needs some corrections are tests - instantiate messages have a new field. I leave it to you as an exercise. -Now we have everything we need to implement donating funds to admins. First, a minor update to the `Cargo.toml` - we +What also need to update some of the tests since instantiate messages now have a new field. We leave this to you as an exercise. + +Now we have everything we need to implement donating funds to admins. First, a minor update to the `Cargo.toml` as we will use an additional utility crate: ```toml @@ -759,22 +757,20 @@ mod exec { ``` Sending the funds to another contract is performed by adding bank messages to -the response. The blockchain would expect any message which is returned in -contract response as a part of an execution. This design is related to an actor -model implemented by CosmWasm. The whole actor model will be described in -detail later. For now, you can assume this is a way to handle token transfers. -Before sending tokens to admins, we have to calculate the amount of donation -per admin. It is done by searching funds for an entry describing our donation -token and dividing the number of tokens sent by the number of admins. Note that -because the integral division is always rounding down. - -As a consequence, it is possible that not all tokens sent as a donation would -end up with no admins accounts. Any leftover would be left on our contract -account forever. There are plenty of ways of dealing with this issue - figuring -out one of them would be a great exercise. +the response. The blockchain will expect any message that is returned in the +contract response as a part of an execution. This design is related to the "Actor +Model" implemented by CosmWasm. The model will be described in +detail later. For now, you can assume that this is a convenient way to handle token transfers. +Before sending the tokens to the admins, we have to calculate the amount of donation +per admin. This is done by searching the funds for an entry describing our donation +token and dividing the number of tokens sent by the number of admins. Since +the integral division always rounds down, it is possible that not all of the tokens sent as a donation would +end up being sent an admin's account. Any remainder would be left on our contract +account forever. There are a number of different ways of dealing with this issue - figuring +out one of them will be a great exercise! The last missing part is updating the `ContractError` - the `must_pay` call -returns a `cw_utils::PaymentError` which we can't convert to our error type +returns a `cw_utils::PaymentError` that we can't convert to our custom error type yet: ```rust,noplayground @@ -793,14 +789,11 @@ pub enum ContractError { } ``` -As you can see, to handle incoming funds, I used the utility function - I -encourage you to take a look at [its -implementation](https://docs.rs/cw-utils/0.13.4/src/cw_utils/payment.rs.html#32-39) - -this would give you a good understanding of how incoming funds are structured -in `MessageInfo`. +As you can see, to handle incoming funds we used the utility function - you're +encouraged to take a look at [its +implementation](https://docs.rs/cw-utils/0.13.4/src/cw_utils/payment.rs.html#32-39). This should give you a good understanding of how incoming funds are structured in `MessageInfo`. -Now it's time to check if the funds are distributed correctly. The way for that -is to write a test. +Now it's time to check if the funds are being distributed correctly by writing another test. ```rust,noplayground # use crate::error::ContractError; @@ -1219,17 +1212,17 @@ mod tests { } ``` -Fairly simple. I don't particularly appreciate that every balance check is -eight lines of code, but it can be improved by enclosing this assertion into a -separate function, probably with the +This is fairly straightforward, though you may not particularly appreciate that every balance check is +eight lines of code! It can be improved by enclosing this assertion in a +separate function, probably with the use of the [`#[track_caller]`](https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-track_caller-attribute) attribute. -The critical thing to talk about is how `app` creation changed. Because we need -some initial tokens on a `user` account, instead of using the default -constructor, we have to provide it with an initializer function. Unfortunately, +The critical thing to discuss is how `app` creation changed. As we need +some initial tokens in a `user`'s account, instead of using the default +constructor we have to provide it with an initializer function. Unfortunately, the [`new`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html#method.new) -documentation is not easy to follow - even if a function is not very +documentation is not very easy to follow, even if our required function is not very complicated. What it takes as an argument is a closure with three arguments - the [`Router`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.Router.html) @@ -1244,14 +1237,14 @@ function sits. ## Plot Twist! -As we covered most of the important basics about building Rust smart contracts, I have a serious exercise for you. +As we have now covered the most important basics required to buildi smart contracts in Rust, we have a serious exercise for you. The contract we built has an exploitable bug. All donations are distributed equally across admins. However, every -admin is eligible to add another admin. And nothing is preventing the admin from adding himself to the list and -receiving twice as many rewards as others! +admin is eligible to add another admin... and nothing is preventing the admin from adding himself to the list and +receiving twice as many rewards as the others! -Try to write a test that detects such a bug, then fix it and ensure the bug nevermore occurs. +Try to write a test that detects such a bug, then fix it and ensure the bug no longer occurs! -Even if the admin cannot add the same address to the list, he can always create new accounts and add them, but this -is something unpreventable on the contract level, so do not prevent that. Handling this kind of case is done by -properly designing whole applications, which is out of this chapter's scope. +Of course, even if the admin cannot add the same address to the list, they could always create new accounts and add them, but this +is something unpreventable on the contract level, so you don't need to prevent that here. Handling this kind of case is done by the +proper design of whole applications which is out of scope for this chapter. diff --git a/src/basics/good-practices.md b/src/basics/good-practices.md index df797e6..85f5aca 100644 --- a/src/basics/good-practices.md +++ b/src/basics/good-practices.md @@ -1,15 +1,14 @@ # Good practices -All the relevant basics are covered. Now let's talk about some good practices. +All the relevant basics have been covered, so now let's talk about some good practices. ## JSON renaming -Due to Rust style, all our message variants are spelled in a -[camel-case](https://en.wikipedia.org/wiki/CamelCase). It is standard practice, -but it has a drawback - all messages are serialized and deserialized by serde -using those variant names. The problem is that it is more common to use [snake -cases](https://en.wikipedia.org/wiki/Snake_case) for field names in the JSON -world. Hopefully, there is an effortless way to tell serde, to change the names +Due to Rust style, all our message variants are spelled using +[camel-case](https://en.wikipedia.org/wiki/CamelCase). This is standard practice, +but it has a drawback since all messages are serialized and deserialized by serde +using those variant names. The problem is that in the JSON world it is more common to use [snake +cases](https://en.wikipedia.org/wiki/Snake_case) for field names. Thankfully, there is an effortless way to tell serde to change the names casing for serialization purposes. Let's update our messages with a `#[serde]` attribute: @@ -54,9 +53,9 @@ pub enum QueryMsg { ## JSON schema -Talking about JSON API, it is worth mentioning JSON Schema. It is a way of defining a shape for JSON messages. -It is good practice to provide a way to generate schemas for contract API. The problem is that writing JSON -schemas by hand is a pain. The good news is that there is a crate that would help us with that. Go to the `Cargo.toml`: +Talking of the JSON API, it is worth mentioning JSON Schema. This is a way of defining a shape for JSON messages. +It is good practice to provide a way to generate schemas for our contract API. The problem is that writing JSON +schemas by hand is a pain. The good news is that there is a crate that can help us. Go to the `Cargo.toml` and update as follows: ```toml [package] @@ -79,7 +78,7 @@ cosmwasm-schema = "1.1.4" cw-multi-test = "0.13.4" ``` -There is one additional change in this file - in `crate-type` I added "rlib". "cdylib" crates cannot be used as typical +There is one additional change in this file - in `crate-type` we've added "rlib". "cdylib" crates cannot be used as normal Rust dependencies. As a consequence, it is impossible to create examples for such crates. Now go back to `src/msg.rs` and add a new derive for all messages: @@ -124,10 +123,9 @@ pub enum QueryMsg { } ``` -You may argue that all those derives look slightly clunky, and I agree. -Hopefully, the +All those derives look slightly clunky, but thankfully the [`cosmwasm-schema`](https://docs.rs/cosmwasm-schema/1.1.4/cosmwasm_schema/#) -crate delivers a utility `cw_serde` macro, which we can use to reduce a +crate delivers a utility `cw_serde` macro which we can use to reduce the amount of boilerplate: ```rust,noplayground @@ -164,8 +162,8 @@ pub enum QueryMsg { } ``` -Additionally, we have to derive the additional `QueryResponses` trait for our -query message to correlate the message variants with responses we would +Additionally, we have to derive the `QueryResponses` trait for our +query message to be able to correlate the message variants with responses we shall generate for them: ```rust,noplayground @@ -205,12 +203,12 @@ pub enum QueryMsg { } ``` -The `QueryResponses` is a trait that requires the `#[returns(...)]` attribute +`QueryResponses` is a trait that requires the `#[returns(...)]` attribute to all your query variants to generate additional information about the query-response relationship. -Now, we want to make the `msg` module public and accessible by crates depending -on our contract (in this case - for schema example). Update a `src/lib.rs`: +Now, we need to make the `msg` module public and accessible by crates that depend +on our contract (in this case - for schema example). Update `src/lib.rs`: ```rust,noplayground # use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; @@ -248,11 +246,11 @@ pub mod state; # } ``` -I changed the visibility of all modules - as our crate can now be used as a dependency. -If someone would like to do so, he may need access to handlers or state. +We've changed the visibility of all modules so our crate can now be used as a dependency. +Someone using it as a dependency may need access to handlers or state. -The next step is to create a tool generating actual schemas. We will do it by creating -an binary in our crate. Create a new `bin/schema.rs` file: +The next step is to create a tool generating actual schemas. We'll do this by creating +a binary in our crate. Create a new `bin/schema.rs` file: ```rust,noplayground use contract::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -278,12 +276,11 @@ Removing "/home/hashed/confio/git/book/examples/03-basics/schema/contract.json" Exported the full API as /home/hashed/confio/git/book/examples/03-basics/schema/contract.json ``` -I encourage you to go to generated file to see what the schema looks like. +You're encouraged to go and look at the generated file to see what the schema looks like. - -The problem is that, unfortunately, creating this binary makes our project fail -to compile on the Wasm target - which is, in the end, the most important one. -Hopefully, we don't need to build the schema binary for the Wasm target - let's +Unforunately, creating this binary makes our project fail +to compile on the Wasm target - which is, in the end, the most important one! +Thankfully, we don't need to build the schema binary for the Wasm target, so let's align the `.cargo/config` file: ```toml @@ -294,17 +291,17 @@ schema = "run schema" ``` The `--lib` flag added to `wasm` cargo aliases tells the toolchain to build -only the library target - it would skip building any binaries. Additionally, I -added the convenience `schema` alias so that one can generate schema calling -simply `cargo schema`. +only the library target and skip building any binaries. In addition we have +added the convenience `schema` alias so that we can generate the schema simply by calling +`cargo schema`. ## Disabling entry points for libraries -Since we added the "rlib" target for the contract, it is, as mentioned before, useable as a dependency. -The problem is that the contract depended on ours would have Wasm entry points generated twice - once -in the dependency and once in the final contract. We can work this around by disabling generating Wasm -entry points for the contract if the crate is used as a dependency. We would use -[feature flags](https://doc.rust-lang.org/cargo/reference/features.html) for that. +As mentioned before, since we have added the "rlib" target for the contract, it is now useable as a dependency. +The problem is that a contract dependent on ours would have Wasm entry points generated twice - once +in the dependency and once in the final contract! We can work around this by disabling generating Wasm +entry points for the contract if the crate is used as a dependency. We use +[feature flags](https://doc.rust-lang.org/cargo/reference/features.html) for this. Start with updating `Cargo.toml`: @@ -313,8 +310,8 @@ Start with updating `Cargo.toml`: library = [] ``` -This way, we created a new feature for our crate. Now we want to disable the `entry_point` attribute on -entry points - we will do it by a slight update of `src/lib.rs`: +The above adjustment creates a new feature for our crate. Next we want to disable the `entry_point` attribute on +entry points, which we will do with a slight update of `src/lib.rs`: ```rust,noplayground # use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; @@ -354,10 +351,10 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { The [`cfg_attr`](https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute) attribute is a conditional compilation attribute, similar to the `cfg` we used before for the test. It expands to the given attribute if -the condition expands to true. In our case - it would expand to nothing if the feature "library" is enabled, or it -would expand just to `#[entry_point]` in another case. +the condition expands to true. In our case, it would expand to nothing if the feature "library" is enabled, otherwise +it would expand to `#[entry_point]`. -Since now to add this contract as a dependency, don't forget to enable the feature like this: +When adding this contract as a dependency, don't forget to enable the feature like this: ```toml [dependencies] diff --git a/src/basics/multitest-intro.md b/src/basics/multitest-intro.md index ff22ec0..3f8fcf0 100644 --- a/src/basics/multitest-intro.md +++ b/src/basics/multitest-intro.md @@ -1,14 +1,14 @@ # Introducing multitest -Let me introduce the [`multitest`](https://crates.io/crates/cw-multi-test) - -library for creating tests for smart contracts in Rust. +Here we introduce the [`multitest`](https://crates.io/crates/cw-multi-test) - +a Rust library for creating tests for smart contracts. -The core idea of `multitest` is abstracting an entity of contract and -simulating the blockchain environment for testing purposes. The purpose of this -is to be able to test communication between smart contracts. It does its job -well, but it is also an excellent tool for testing single-contract scenarios. +The core idea of `multitest` is abstracting entities of contracts and +simulating the blockchain environment for testing purposes. This allows us to test +communication between smart contracts. It does this job well, but it is also an +excellent tool for testing single-contract scenarios. -First, we need to add a multitest to our `Cargo.toml`. +First, we need to add multitest to our `Cargo.toml`. ```toml [package] @@ -27,12 +27,12 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] } cw-multi-test = "0.13.4" ``` -I added a new +Here we've added a new [`[dev-dependencies]`](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#development-dependencies) section with dependencies not used by the final binary -but which may be used by tools around the development process - for example, tests. +but which may be used by tools around the development process such as tests. -When we have the dependency ready, update our test to use the framework: +Once we have the dependency set up, we can update our test to use the framework: ```rust,noplayground # use crate::msg::{GreetResp, QueryMsg}; @@ -119,42 +119,37 @@ mod tests { } ``` -You probably notice that I added the function for an `execute` entry point. I didn't add the entry point -itself or the function's implementation, but for the multitest purposes contract has to contain at least -instantiate, query, and execute handlers. I attributed the function as +You may have noticed that we added the parameter for an `execute` entry point. Although it wasn't necessary +to add either the entry point itself or the function's implementation, multitest requires that the contract +contain at least instantiate, query, and execute handlers. By attributing the function with [`#[allow(dead_code)]`](https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes), -so, `cargo` will not complain about it not being used anywhere. Enabling it for tests only with `#[cfg(test)]` -would also be a way. +, we ensure that `cargo` will not complain about it not being used anywhere. Alternatively, we could enable it for tests only by using `#[cfg(test)]`. -Then at the beginning of the test, I created the +At the beginning of the test, there is an [`App`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html#) -object. It is a core multitest entity representing the virtual blockchain on +object. This is a core multitest entity representing the virtual blockchain on which we will run our contracts. As you can see, we can call functions on it -just like we would interact with blockchain using `wasmd`! +just like we would interact with the blockchain using `wasmd`! -Right after creating `app`, I prepared the representation of the `code`, which -would be "uploaded" to the blockchain. As multitests are just native Rust -tests, they do not involve any Wasm binaries, but this name matches well what -happens in a real-life scenario. We store this object in the blockchain with +Right after creating `app`, we prepare the representation of the `code` which +will be "uploaded" to the blockchain. As multitests are just native Rust +tests, they do not involve any Wasm binaries, but this name represents pretty well what +happens in a real-life scenario. We store this object on the blockchain with the [`store_code`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html#method.store_code) -function, and as a result, we are getting the code id - we would need it to -instantiate a contract. +function, and receive back the code id as a result, which is required to instantiate the contract. Instantiation is the next step. In a single [`instantiate_contract`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/trait.Executor.html#method.instantiate_contract) -call, we provide everything we would provide via `wasmd` - the contract code id, the address which performs instantiation, +call, we provide everything we would provide via `wasmd` - the contract code id, the address which performs instantiation, the message triggering it, and any funds sent with the message (again - empty for now). We are adding the contract label and its admin for migrations - set to `None` as we don't need it yet. -the message triggering it, and any funds sent with the message (again - empty for now). We are adding the contract label -and its admin for migrations - `None`, as we don't need it yet. - -And after the contract is online, we can query it. The +Once the contract is online, we can query it. The [`wrap`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html?search=in#method.wrap) function is an accessor -for querying Api (queries are handled a bit differently than other calls), and the +for the querying API (queries are handled a bit differently to other calls), and the [`query_wasm_smart`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.QuerierWrapper.html#method.query_wasm_smart) -queries are given a contract with the message. Also, we don't need to care about query results as `Binary` - multitest +queries are given a contract along with the message. We don't need to care about query results being `Binary` - multitest assumes that we would like to deserialize them to some response type, so it takes advantage of Rust type elision to -provide us with a nice Api. +provide us with a nice API. -Now it's time to rerun the test. It should still pass, but now we nicely abstracted the testing contract as a whole, -not some internal functions. The next thing we should probably cover is making the contract more interesting +Now it's time to rerun the test. It should still pass, but now we nicely abstracted testing contract as a whole, +rather than just some internal functions. The next thing we'll cover is making the contract more interesting by adding some state. diff --git a/src/basics/query-testing.md b/src/basics/query-testing.md index efa4a86..960672a 100644 --- a/src/basics/query-testing.md +++ b/src/basics/query-testing.md @@ -1,7 +1,7 @@ # Testing a query -Last time we created a new query, now it is time to test it out. We will start with the basics - -the unit test. This approach is simple and doesn't require knowledge besides Rust. Go to the +The last thing we did was create a new query, now it's time to test it out. We shall start with the basics - +the unit tests. This approach is simple and doesn't require any special knowledge beyond Rust. Go to the `src/contract.rs` and add a test in its module: ```rust,noplayground @@ -56,9 +56,9 @@ mod tests { } ``` -If you ever wrote a unit test in Rust, nothing should surprise you here. Just a -simple test-only module contains local function unit tests. The problem is - this -test doesn't build yet. We need to tweak our message types a bit. Update the `src/msg.rs`: +If you have ever written a unit test in Rust before, nothing should surprise you here. This is just a +simple test-only module containing local unit test functions. The problem is, this +test doesn't compile yet. We need to tweak our message types a bit. Update the `src/msg.rs`: ```rust,noplayground # use serde::{Deserialize, Serialize}; @@ -74,18 +74,18 @@ pub enum QueryMsg { } ``` -I added three new derives to both message types. [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) +Three new derives have been added to both message types. [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) is required to allow comparing types for equality - so we can check if they are equal. The [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) is a trait generating debug-printing utilities. It is used by [`assert_eq!`](https://doc.rust-lang.org/std/macro.assert_eq.html) to -display information about mismatch if an assertion +display information about the mismatch if an assertion fails. Note that because we are not testing the `QueryMsg` in any way, the additional trait derives are optional. Still, it is a good practice to make all messages both `PartialEq` and `Debug` for testability and consistency. -The last one, [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) is not needed for now yet, -but it is also good practice to allow messages to be cloned around. We will also require that -later, so I added it already not to go back and forth. +The last one, [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) is not needed just yet, +but it is also good practice to allow messages to be cloned. We will also require it +later, so we may as well add it now. Now we are ready to run our test: @@ -105,7 +105,7 @@ Yay! Test passed! Now let's go a step further. The Rust testing utility is a friendly tool for building even higher-level tests. We are currently testing smart contract internals, but if you think about how your smart contract -is visible from the outside world. It is a single entity that is triggered by some input messages. We can +is viewed from the outside world, it is as a single entity that is triggered by some input messages. We can create tests that treat the whole contract as a black box by testing it via our `query` function. Let's update our test: @@ -171,23 +171,23 @@ mod tests { ``` We needed to produce two entities for the `query` functions: the `deps` and `env` instances. -Hopefully, `cosmwasm-std` provides utilities for testing those - +Thankfully, `cosmwasm-std` provides utilities for testing those - [`mock_dependencies`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/testing/fn.mock_dependencies.html) and [`mock_env`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/testing/fn.mock_env.html) functions. -You may notice the dependencies mock of a type +You may notice that the mock dependencies are of a type [`OwnedDeps`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.OwnedDeps.html) instead -of `Deps`, which we need here - this is why the +of `Deps`, which is what we need here - this is why the [`as_ref`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.OwnedDeps.html#method.as_ref) -function is called on it. If we looked for a `DepsMut` object, we would use +function is called on it. If we were looking for a `DepsMut` object, we would use [`as_mut`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.OwnedDeps.html#method.as_mut) instead. -We can rerun the test, and it should still pass. But when we think about that test reflecting -the actual use case, it is inaccurate. The contract is queried, but it was never instantiated! -In software engineering, it is equivalent to calling a getter without constructing an object - -taking it out of nowhere. It is a lousy testing approach. We can do better: +We can rerun the test, and it should still pass. However, if we think about this carefully, this flow +does not accurately describe the real use case. The contract is queried, but it was never instantiated! +In software engineering, this is equivalent to calling a getter without constructing an object - +taking it out of nowhere! It's a lousy testing approach. We can do better: ```rust,noplayground @@ -257,9 +257,9 @@ mod tests { } ``` -A couple of new things here. First, I extracted the `deps` and `env` variables to their variables -and passed them to calls. The idea is that those variables represent some blockchain persistent state, -and we don't want to create them for every call. We want any changes to the contract state occurring +A couple of new things here. First, we extracted `deps` and `env` and passed their respective variables +to the calls. The idea is that those variables represent some persistent blockchain state, +and we don't want to re-create them for every call. We want any changes to the contract state occurring in `instantiate` to be visible in the `query`. Also, we want to control how the environment differs on the query and instantiation. @@ -267,15 +267,13 @@ The `info` argument is another story. The message info is unique for each messag `info` mock, we must pass two arguments to the [`mock_info`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/testing/fn.mock_info.html) function. -First is the address performing a call. It may look strange to pass `sender` as an address instead of some -mysterious `wasm` followed by hash, but it is a valid address. For testing purposes, such addresses are -typically better, as they are way more verbose in case of failing tests. +The first is the address performing a call. It may look strange to pass `sender` as an address instead of some +mysterious one (`wasm` followed by hash), but it is a valid address. In fact, for testing purposes such addresses are +typically better, as they are far more verbose in the case of failing tests. -The second argument is funds sent with the message. For now, we leave it as an empty slice, as I don't want -to talk about token transfers yet - we will cover it later. +The second argument describes the funds sent with the message. For now, we can leave it as an empty slice since we +don't want to deal with token transfers just yet. -So now it is more a real-case scenario. I see just one problem. I say that the contract is a single black -box. But here, nothing connects the `instantiate` call to the corresponding `query`. It seems that we assume -there is some global contract. But it seems that if we would like to have two contracts instantiated differently -in a single test case, it would become a mess. If only there would be some tool to abstract this for us, wouldn't -it be nice? +So now the scenario is more realistic. However, there is still one problem. We said that the contract is a single black box, but here nothing connects the `instantiate` call to the corresponding `query`. It seems that we assume +there is some global contract. If we were to have two contracts instantiated differently +in a single test case, this would become a mess. Wouldn't it be nice if there was some convenient tool to abstract this for us? diff --git a/src/basics/query.md b/src/basics/query.md index 5d18c63..bf6d9e1 100644 --- a/src/basics/query.md +++ b/src/basics/query.md @@ -1,10 +1,9 @@ # Creating a query -We have already created a simple contract reacting to an empty instantiate message. Unfortunately, it -is not very useful. Let's make it a bit reactive. +We have already created a simple contract reacting to an empty `instantiate` message. Unfortunately, it's +not very useful. Let's make it a bit more reactive. -First, we need to add [`serde`](https://crates.io/crates/serde) crate to our dependencies. It would help us with the serialization and -deserialization of query messages. Update the `Cargo.toml`: +First, we need to add the [`serde`](https://crates.io/crates/serde) crate to our dependencies. It will help us with the serialization and deserialization of query messages. Update the `Cargo.toml`: ```toml [package] @@ -57,49 +56,49 @@ pub fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult { } ``` -Note that I omitted the previously created instantiate endpoint for simplicity - -not to overload you with code, I will always only show lines that changed in the code. +Note that we omitted the previously created `instantiate` endpoint for simplicity so as +not to overload you with code. From now on, we shall often only show lines in the code that actually +changed. -We first need a structure we will return from our query. We always want to return something -which is serializable. We are just deriving the +We first need a structure we will return from our query. Since we always want to return something +which is serializable, here we are just deriving the [`Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) and -[`Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html) traits from `serde` crate. - -Then we need to implement our entry point. It is very similar to the `instantiate` one. The -first significant difference is a type of `deps` argument. For `instantiate`, it was a `DepMut`, -but here we went with a [`Deps`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Deps.html) -object. That is because the query can never alter the smart -contract's internal state. It can only read the state. It comes with some consequences - for example, -it is impossible to implement caching for future queries (as it would require some data cache to write -to). - -The other difference is the lack of the `info` argument. The reason here is that the entry point which -performs actions (like instantiation or execution) can differ how an action is performed based on the -message metadata - for example, they can limit who can perform an action (and do so by checking the -message `sender`). It is not a case for queries. Queries are supposed just purely to return some +[`Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html) traits using the `serde` crate. + +Next we need to implement our entry point. It is very similar to the `instantiate` one. The +first significant difference is an argument of type `Deps`. For `instantiate`, it was a `DepMut`, +but here we use a [`Deps`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Deps.html) +object. This is because the query can never alter the smart +contract's internal state. It can only read the state. This has some consequences - for example, +it is impossible to implement caching for future queries (as this would require some sort of data cache +to write to). + +The other difference is the lack of the `info` argument. An entry point which +performs actions (such as instantiation or execution) may alter how the action is performed based +on the message metadata - for example checking who can perform an action - but this is not the case queries. Queries are purely supposed to return some transformed contract state. It can be calculated based on some chain metadata (so the state can -"automatically" change after some time), but not on message info. +"automatically" change after some time), but is not based on message info. -Note that our entry point still has the same `Empty` type for its `msg` argument - it means that the +Note that our entry point still has the same `Empty` type for its `msg` argument. This means that the query message we would send to the contract is still an empty JSON: `{}` -The last thing that changed is the return type. Instead of returning the `Response` type on the success -case, we return an arbitrary serializable object. This is because queries are not using a typical actor -model message flow - they cannot trigger any actions nor communicate with other contracts in ways different -than querying them (which is handled by the `deps` argument). The query always returns plain data, which -should be presented directly to the querier. +The last thing that we've changed is the return type. Instead of returning the `Response` type in the success +case, we return an arbitrary serializable object. This is because queries are not using a typical Actor +Model message flow (discussed later) and they cannot trigger any actions nor communicate with other contracts other than just +querying them (which is handled by the `Deps` argument). The query always returns plain data, which +should be presented directly to the enquirer. -Now take a look at the implementation. Nothing complicated happens there - we create an object we want +Now take a look at the implementation. Nothing complicated happens here - we create an object we want to return and encode it to the [`Binary`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Binary.html) type using the [`to_binary`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/fn.to_binary.html) function. ## Improving the message -We have a query, but there is a problem with the query message. It is always an empty JSON. It is terrible - -if we would like to add another query in the future, it would be difficult to have any reasonable distinction +Although we have a query, there is a problem with the query message. It is always an empty JSON. This isn't +great - if we would like to add another query in the future, it would be difficult to distinguish between query variants. -In practice, we address this by using a non-empty query message type. Improve our contract: +In practice, we address this by using a non-empty query message type. Here is an improved version of our contract: ```rust,noplayground # use cosmwasm_std::{ @@ -143,28 +142,27 @@ pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } ``` -Now we introduced a proper message type for the query message. It is an +Now we have introduced a proper message type for the query message. It is an [enum](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html), and by -default, it would serialize to a JSON with a single field - the name of the field -will be an enum variant (in our case - always "greet" - at least for now), and the +default, it would serialize to a JSON with a single field. The name of the field +will be an enum variant (in our case - always "Greet" - at least for now). The value of this field would be an object assigned to this enum variant. Note that our enum has no type assigned to the only `Greet` variant. Typically -in Rust, we create such variants without additional `{}` after the variant name. Here the -curly braces have a purpose - without them, the variant would serialize to just a string +in Rust, we create such variants without the additional `{}` after the variant name. Here the +curly braces serve a purpose: without them, the variant would serialize to just a string type - so instead of `{ "greet": {} }`, the JSON representation of this variant would be -`"greet"`. This behavior brings inconsistency in the message schema. It is, generally, -a good habit to always add the `{}` to serde serializable empty enum variants - for better -JSON representation. - -But now, we can still improve the code further. Right now, the `query` function has two -responsibilities. The first is obvious - handling the query itself. It was the first -assumption, and it is still there. But there is a new thing happening there - the query -message dispatching. It may not be obvious, as there is a single variant, but the query -function is an excellent way to become a massive unreadable `match` statement. To make +`"greet"`. This behavior leads to inconsistency in the message schema. It is generally +a good habit to always add the `{}` to serde-serializable empty enum variants in order to +ensure better and more consistent JSON representation. + +We can improve the code still further. Right now, the `query` function has two +responsibilities. The first is obvious - handling the query itself. This was the first +assumption, and it is still there. But there is another thing happening here as well - the query +message dispatching. It may not be obvious, as there is just a single variant in our code, but +the query function is an excellent way to create massive unreadable `match` statements. To make the code more [SOLID](https://en.wikipedia.org/wiki/SOLID), we will refactor it and -take out handling the `greet` message to a -separate function. +give responsibility for handling of the `Greet` message to a separate function. ```rust,noplayground # use cosmwasm_std::{ @@ -214,32 +212,31 @@ mod query { } ``` -Now it looks much better. Note there are a couple of additional improvements. I -renamed the query-response type `GreetResp` as I may have different responses -for different queries. I want the name to relate only to the variant, not the +Now it looks much better. Note that there are a couple of additional improvements here as well. We +renamed the query-response type `GreetResp` as we may have different responses +for different queries. We want the name to relate only to the variant, not the whole message. -Next is enclosing my new function in the module `query`. It makes it easier to -avoid name collisions - I can have the same variant for queries and execution -messages in the future, and their handlers would lie in separate namespaces. +The other improvement involves enclosing the new function in the module `query`. This +makes it easier to avoid name collisions - we can have the same variant for queries and +execution messages in the future, and their handlers would lie in separate namespaces. A questionable decision may be returning `StdResult` instead of `GreetResp` -from `greet` function, as it would never return an error. It is a matter of -style, but I prefer consistency over the message handler, and the majority of -them would have failure cases - e.g. when reading the state. +from the `greet` function, as it would never return an error. It is a matter of +style, since you may prefer consistency over the message handler as the majority of +them would have failure cases e.g. when reading the state. Also, you might pass `deps` and `env` arguments to all your query handlers for -consistency. I'm not too fond of this, as it introduces unnecessary boilerplate -which doesn't read well, but I also agree with the consistency argument - I -leave it to your judgment. +consistency. On the one hand, you may consider that it introduces unnecessary boilerplate +which doesn't read well. It's up to you - we leave it up to your judgment. ## Structuring the contract You can see that our contract is becoming a bit bigger now. About 50 lines are maybe -not so much, but there are many different entities in a single file, and I think we -can do better. I can already distinguish three different types of entities in the code: +not so much, but there are many different entities in a single file, and we +can do better. We can already distinguish three different types of entity in the code: entry points, messages, and handlers. In most contracts, we would divide them across -three files. Start with extracting all the messages to `src/msg.rs`: +three files. Let's start with extracting all the messages to `src/msg.rs`: ```rust,noplayground use serde::{Deserialize, Serialize}; @@ -255,8 +252,8 @@ pub enum QueryMsg { } ``` -You probably noticed that I made my `GreetResp` fields public. It is because they have -to be now accessed from a different module. Now move forward to the `src/contract.rs` file: +You'll have noticed that the `GreetResp` fields are now public. This is because they now +have to be accessed from a different module. Next we can move on to the `src/contract.rs` file: ```rust,noplayground use crate::msg::{GreetResp, QueryMsg}; @@ -294,16 +291,14 @@ mod query { } ``` -I moved most of the logic here, so my `src/lib.rs` is just a very thin library entry with nothing -else but module definitions and entry points definition. I removed the `#[entry_point]` attribute -from my `query` function in `src/contract.rs`. I will have a function with this attribute. -Still, I wanted to split functions' responsibility further - not the `contract::query` function is -the top-level query handler responsible for dispatching the query message. The `query` function on -crate-level is only an entry point. It is a subtle distinction, but it will make sense in the future -when we would like not to generate the entry points but to keep the dispatching functions. I introduced -the split now to show you the typical contract structure. +We have moved most of the logic here, so that `src/lib.rs` is now just a very thin library entry with nothing +other than module definitions and entry point definitions. The `#[entry_point]` attribute +has been removed from the `query` function in `src/contract.rs`. We shall appply this attribute to another function instead. The main objective here was to split the functions even further by responsibility: now the `contract::query` function is the top-level query handler responsible for dispatching the query message, while the `query` function +on crate-level is only an entry point. It is a subtle distinction, but will make sense in the future +when we would like to keep dispatching functions without generating entry points. The split is introduced +at this point in order to demonstrate the typical contract structure. -Now the last part, the `src/lib.rs` file: +Finally, the `src/lib.rs` file: ```rust,noplayground use cosmwasm_std::{ @@ -328,6 +323,6 @@ pub fn query(deps: Deps, env: Env, msg: msg::QueryMsg) } ``` -Straightforward top-level module. Definition of submodules and entry points, nothing more. +This is a straightforward top-level module, containing nothing more than the definition of submodules and entry points. -Now, when we have the contract ready to do something, let's go and test it. +Now that we have the contract ready to do something, let's go ahead and test it! \ No newline at end of file diff --git a/src/basics/rust-project.md b/src/basics/rust-project.md index a0beaee..e67653e 100644 --- a/src/basics/rust-project.md +++ b/src/basics/rust-project.md @@ -1,12 +1,12 @@ # Create a Rust project -As smart contracts are Rust library crates, we will start with creating one: +As smart contracts are just Rust library crates, so let's start by creating one: ``` $ cargo new --lib ./empty-contract ``` -You created a simple Rust library, but it is not yet ready to be a smart contract. The first thing +We created a simple Rust library, but it is not yet ready to be a smart contract. The first thing to do is to update the `Cargo.toml` file: ```toml @@ -23,13 +23,13 @@ cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } ``` -As you can see, I added a `crate-type` field for the library section. Generating the `cdylib` is +As you can see, we added a `crate-type` field for the library section. Generating the `cdylib` is required to create a proper web assembly binary. The downside of this is that such a library cannot -be used as a dependency for other Rust crates - for now, it is not needed, but later we will show -how to approach reusing contracts as dependencies. +be used as a dependency for other Rust crates. We don't need this anyway just now, but later we shall +demonstrate an approach allowing us to re-use contracts as dependencies. -Additionally, there is one core dependency for smart contracts: the `cosmwasm-std`. This crate is a +Additionally, there is one core dependency for smart contracts that we need: `cosmwasm-std`. This crate is a standard library for smart contracts. It provides essential utilities for communication with the -outside world and a couple of helper functions and types. Every smart contract we will build will +outside world and a couple of helper functions and types. Every smart contract we build will use this dependency. diff --git a/src/basics/state.md b/src/basics/state.md index a3b4c1a..7193b4e 100644 --- a/src/basics/state.md +++ b/src/basics/state.md @@ -1,11 +1,11 @@ # Contract state -The contract we are working on already has some behavior that can answer a query. Unfortunately, it is -very predictable with its answers, and it has nothing to alter them. In this chapter, I introduce the -notion of state, which would allow us to bring true life to a smart contract. +The contract we are working on already has some basic behavior in that it can answer a query. Unfortunately, it is +very predictable with its answers, and it has no ability to alter them. In this chapter, we introduce the +notion of state, which allows us to bring the smart contract to life. -The state would still be static for now - it would be initialized on contract instantiation. The state -would contain a list of admins who would be eligible to execute messages in the future. +The state will still be static for now - it will be initialized on contract instantiation. The state +will contain a list of admins who will be eligible to execute messages in the future. The first thing to do is to update `Cargo.toml` with yet another dependency - the [`storage-plus`](https://crates.io/crates/cw-storage-plus) crate with high-level bindings for CosmWasm @@ -29,7 +29,7 @@ cw-storage-plus = "0.13.4" cw-multi-test = "0.13.4" ``` -Now create a new file where you will keep a state for the contract - we typically call it `src/state.rs`: +Now we create a new file in which we can keep a state for the contract - we typically call it `src/state.rs`: ```rust,noplayground use cosmwasm_std::Addr; @@ -38,7 +38,7 @@ use cw_storage_plus::Item; pub const ADMINS: Item> = Item::new("admins"); ``` -And make sure we declare the module in `src/lib.rs`: +And we make sure to declare the module in `src/lib.rs`: ```rust,noplayground # use cosmwasm_std::{ @@ -65,25 +65,25 @@ mod state; ``` The new thing we have here is the `ADMINS` constant of type `Item>`. You could ask an excellent -question here - how is the state constant? How do I modify it if it is a constant value? +question here - how can the state be constant? How do I modify it if it is a constant value? -The answer is tricky - this constant is not keeping the state itself. The state is stored in the +The answer is a bit tricky - this constant is not actually storing the state itself. The state is stored on the blockchain and is accessed via the `deps` argument passed to entry points. The storage-plus constants are just accessor utilities helping us access this state in a structured way. -In CosmWasm, the blockchain state is just massive key-value storage. The keys are prefixed with metainformation +In CosmWasm, the blockchain state is just massive key-value storage. The keys are prefixed with meta-information pointing to the contract which owns them (so no other contract can alter them in any way), but even after -removing the prefixes, the single contract state is a smaller key-value pair. +removing the prefixes, the single contract state is just a smaller key-value pair. -`storage-plus` handles more complex state structures by additionally prefixing items keys intelligently. For now, +`storage-plus` handles more complex state structures by additionally prefixing the items keys intelligently. For now, we just used the simplest storage entity - an [`Item<_>`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html), which holds a single optional -value of a given type - -`Vec` in this case. And what would be a key to this item in the storage? It doesn't matter to us - it would -be figured out to be unique, based on a unique string passed to the +value of a given type, in this case +`Vec`. And what would a key to this item in the storage look like? We don't need to worry about it - this will +be figured out to be unique, based on the unique string passed to the [`new`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.new) function. -Before we would go into initializing our state, we need some better instantiate message. Go to `src/msg.rs` and create one: +Before we go into initializing our state, we need a better instantiate message. Go to `src/msg.rs` and create one: ```rust,noplayground # use cosmwasm_std::Addr; @@ -196,7 +196,7 @@ pub fn instantiate( # } ``` -We also need to update the message type on entry point in `src/lib.rs`: +We also need to update the message type on the entry point in `src/lib.rs`: ```rust,noplayground # use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; @@ -223,24 +223,21 @@ pub fn instantiate( # } ``` -Voila, that's all that is needed to update the state! +Voila! That's all that's needed to update the state! -First, we need to transform the vector of strings into the vector of addresses to be stored. We cannot take -addresses as a message argument because not every string is a valid address. It might be a bit confusing when -we were working on tests. Any string could be used in the place of address. Let me explain. +First, we needed to transform the vector of strings into the vector of addresses to be stored. We cannot take +addresses as a message argument because not every string is a valid address. This may seem a bit confusing, +since when we were working on the tests any string could be used in the place of an address! What's going on? -Every string can be technically considered an address. However, not every string is an actual existing blockchain -address. When we keep anything of type `Addr` in the contract, we assume it is a proper address in the blockchain. -That is why the [`addr_validate`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/trait.Api.html#tymethod.addr_validate) -function exits - to check this precondition. +The answer lies in the fact that while every string can indeed technically be considered an address, not every string is an actual existing blockchain address. When we keep anything of type `Addr` in the contract, we assume it is a proper address in the blockchain. +That is why the [`addr_validate`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/trait.Api.html#tymethod.addr_validate) function exits - to check this precondition. Having data to store, we use the [`save`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.save) function to write it into the contract state. Note that the first argument of `save` is [`&mut Storage`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/trait.Storage.html), which is actual blockchain -storage. As emphasized, the `Item` object stores nothing and is just an accessor. It determines how to store the data -in the storage given to it. The second argument is the serializable data to be stored. +storage. As emphasized, the `Item` object stores nothing and is just an accessor. It just determines how to store the data in the storage given to it. The second argument is the actual serializable data to be stored. -It is a good time to check if the regression we have passes - try running our tests: +It is a good time to check if the regression we have passes. Let's try running our tests: ``` > cargo test @@ -269,13 +266,12 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; error: test failed, to rerun pass '--lib' ``` -Damn, we broke something! But be calm. Let's start with carefully reading an error message: +Damn, we broke something! But stay calm. Let's start with carefully reading the error message: > Error parsing into type contract::msg::InstantiateMsg: missing field `admins`', src/contract.rs:80:14 -The problem is that in the test, we send an empty instantiation message in our test, but right now, our endpoint expects -to have an `admin` field. Multi-test framework tests contract from the entry point to results, so sending messages using MT -functions first serializes them. Then the contract deserializes them on the entry. But now it tries to deserialize the +The problem is that in the test, we send an empty instantiation message but our endpoint now expects +to have an `admin` field. The Multi-test framework tests the contract from the entry point to the results, so sending messages using Multi-test functions first serializes them. Then the contract deserializes them on the entry. But now it tries to deserialize the empty JSON to some non-empty message! We can quickly fix it by updating the test: ```rust,noplayground @@ -504,7 +500,7 @@ mod query { # } ``` -Now when we have the tools to test the instantiation, let's write a test case: +Now that we have the tools to test the instantiation, let's write a test case: ```rust,noplayground use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg}; @@ -653,7 +649,7 @@ mod tests { ``` The test is simple - instantiate the contract twice with different initial admins, and ensure the query result -is proper each time. This is often the way we test our contract - we execute bunch o messages on the contract, -and then we query it for some data, verifying if query responses are like expected. +is proper each time. This is often the way we test our contract - we execute a bunch of messages on the contract, +and then we query it for some data, verifying that the query responses are as expected. -We are doing a pretty good job developing our contract. Now it is time to use the state and allow for some executions. +We are doing a pretty good job developing our contract. Now it is time to use the state and allow for some execution. diff --git a/src/cross-contract.md b/src/cross-contract.md index cb91c1c..31d3362 100644 --- a/src/cross-contract.md +++ b/src/cross-contract.md @@ -1,16 +1,16 @@ # Cross contract communication -We already covered creating a single isolating contract. However, SOLID principles tell us that -entities should be as small as reasonably possible - such as they have a -[single responsibility](https://en.wikipedia.org/wiki/Single-responsibility_principle). Entities -we are focusing on now are smart contracts, and we want to make sure that every smart contract has -a sole responsibility it takes care of. +We have covered creating a single isolated contract. However, SOLID principles tell us that +entities should be as small as reasonably possible so that they have a +[single responsibility](https://en.wikipedia.org/wiki/Single-responsibility_principle). The entities +we are focusing on now are smart contracts, and we want to ensure that every smart contract has +the sole responsibility for whatever specific task it takes care of. -But we also want to build complex systems using smart contracts. To do so, we need to be able to -communicate between them. We already talked about what such communication looks like using an actor -model. Now it's time to use this knowledge in practice. +However, we also want to build complex systems using smart contracts. To do so, we need to be able to +communicate between them and we discussed how such communication looks using the Actor +Model. Now it's time to use this knowledge in practice. -In this chapter, we will improve the previously created administration group model to solve the problem -I brought - the possibility of adding own multiple addresses by a single admin to take bigger donation parts. +In this chapter we will improve the administration group model we created before to solve the problem +we presented earlier - the possibility of an admin adding multiple addresses in order to take a greater proportion of the donations. -We would also give admins some work to do besides being admins. +We shall also give admins some work to do besides just being admins. diff --git a/src/cross-contract/design.md b/src/cross-contract/design.md index 5f8dab5..c2fdf98 100644 --- a/src/cross-contract/design.md +++ b/src/cross-contract/design.md @@ -1,33 +1,30 @@ # Design -This time we will start discussing the design of our system a bit. Building multi-contract systems tend to -be a bit more complicated than just isolated contracts, so I want to give you some anchor on what we are -building in this chapter. If you feel lost with a design, don't worry - it will get clear while implementing -contracts. For now, go through it to get a general idea. +It's time to start discussing the design of our system. Building multi-contract systems tends to +be a bit more complicated than just isolated contracts, so its useful to have some kind of anchor first, which is the purpose of this chapter. If you feel lost with the design, don't worry - it will become clearer when we are actually implementing the contracts. For now, let's go through it once to get a general idea. -First, let's think about the problem we want to solve. Our admins are a vector of addresses. Anyone already -an admin can add anyone he wants to the list. But this "anyone" can be a second instance of the same admin -account, so he counts twice for donations! +First, let's think about the problem we want to solve. Our admins are a vector of addresses. Anyone who is already +an admin can add anyone they wish to the list. But this "anyone" can be a second instance of the same admin +account, meaning that they would be counted twice for donations! This issue is relatively simple to fix, but there is another problem - as we already learned, the admin could -create a smart contract which he and only he can withdraw tokens from and register as another admin in the +create a smart contract which he and only he can withdraw tokens from and register it as another admin in the group! Instantiating it multiple times, he can achieve his goal even if we prevent adding the same address multiple times. There would be many distinct addresses that the same person owns. -It looks like an unpleasant situation, but there are ways to manage it. The one we would implement is voting. -Instead of being able to add another admin to the list, admins would be allowed to propose their colleagues -as new admins. It would start a voting process - everyone who was an admin at the time of the proposal creation -would be able to support it. If more than half admins would support the new candidate, he would immediately +It looks like an unpleasant situation, but there are ways to manage it. The one we will implement is voting. +Instead of being able to add another admin to the list unilateraly, admins will be allowed to propose new admins. This would kickstart a voting process and all admins at the time of the proposal creation +would be able to choose to support it. If more than half of the admins support the new candidate, then they immediately become an admin. -It is not the most convoluted voting process, but it would be enough for our purposes. +It is not the most sophisticated voting process, but it will be enough for our demonstration purposes. ## Voting process -To achieve this goal, we would create two smart contracts. First, one would be reused contract from the -[Basics](../basics.md) chapter - it would be an `admin` contract. Additionally, we would add a `voting` contract. -It would be responsible for managing a single voting process. It would be instantiated by an `admin` contract -whenever an admin wants to add his friend to a list. Here is a diagram of the contracts relationship: +To achieve this goal we will create two smart contracts. For the first one we will re-use the contract from the +[Basics](../basics.md) chapter. This will be our `admin` contract. We also need to add a `voting` contract. +It will be responsible for managing a single voting process. It will be instantiated by an `admin` contract +whenever an admin wants to add a colleague to the list. Here is a diagram showing the relationship of the contracts: ```plantuml @startuml @@ -57,7 +54,7 @@ admin o- voting: manages @enduml ``` -Here is adding an admin flowchart - assuming there are 5 admins on the contract already, but 2 of them did nothing: +Here is the flowchart for adding an admin. We assume there are 5 admins on the contract to start with, but 2 of them don't do anything: ```plantuml @startuml @@ -86,14 +83,14 @@ votes -> admin --: add_admin { addr: new_admin } @enduml ``` -I already put some hints about contracts implementation, but I will not go into them yet. +We already gave some hints about how the contracts could be implemented, but we won't go into that just yet. -## Messages forwarding +## Message forwarding -There is one other thing we want to add - some way to give admins work. The `admin` contract would behave like +There is one other thing we want to add: some way to assign the admins some work. The `admin` contract will behave like a proxy to call another contract. That means that some other external contract would just set our `admin` instance as a specific address that can perform executions on it, and admins would perform actions this way. The external -contract would see execution as the admin contract would do it. Here is an updated contracts diagram: +contract would see execution messages sent by the admin contract. Here is an updated contracts diagram: ```plantuml @startuml @@ -143,7 +140,7 @@ deactivate contract ``` Note that the `msg` on `execute` admin contract message is some arbitrary message just forwarded -to the external contract. It would be a base64-encoded message in the real world, but it is +to the external contract. It would be a base64-encoded message in the real world, but this is just an implementation detail. -Ultimately, we will create a simple example of an external contract to understand how to use such a pattern. +Ultimately we will create a simple example of an external contract to understand how to use such a pattern. diff --git a/src/cross-contract/fixing-admin.md b/src/cross-contract/fixing-admin.md index d53b7dc..623f8df 100644 --- a/src/cross-contract/fixing-admin.md +++ b/src/cross-contract/fixing-admin.md @@ -1,15 +1,15 @@ # Fixing admin contract -Now that we know what we want to achieve, we can start by aligning the -contract we already have to become an admin contract. It is primarily -fine at this point, but we want to do a cleanup. +Now that we know what we want to achieve, we can start by converting the +contract we already have into an admin contract. It is mostly +fine at this point, but we want to clean it up. ## Cleaning up queries The first thing to do is to get rid of the `Greet` query - it was good as a starter query example, but it has no practical purpose and only generates noise. -We want to remove the unnecessary variant from the query enum: +Let's remove the unnecessary variant from the query enum: ```rust # use cosmwasm_schema::{cw_serde, QueryResponses}; @@ -40,7 +40,7 @@ pub enum QueryMsg { } ``` -Then we also remove the invalid path in the query dispatcher: +We should also remove the invalid path in the query dispatcher: ```rust pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { @@ -52,23 +52,23 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } ``` -Finally, we remove the irrelevant handler from the `contract::query` module. -We also need to make sure all references to it are gone (eg. if there are any -in the tests). +Finally, we remove the unnecessary handler from the `contract::query` module. +We also need to make sure all references to it are gone (e.g. any references +in the tests if they exist). ## Generating the library output -At the very beginning of the book, we set the `crate-type` in `Cargo.toml` as -`"cdylib"`. It was required to generate the wasm output, but it comes with a -drawback - the dynamic libraries, as this cannot be used as dependencies in -other crates. It was not a problem before, but in practice we often want to -depend contract on others to get access to some types of them - for example, +At the very beginning of the book, we set the `crate-type` in `Cargo.toml` to +`"cdylib"`. This was required to generate the wasm output, but it comes with a +drawback - dynamic libraries cannot be used as dependencies in +other crates. It wasn;t a problem before, but in practice we often want to +enable contracts that depend on others to have access to some of their types - for example, defined messages. -Good for us. It is easy to fix. You might notice that the `crate-type` is an array, +Lucky for us, it's easy to fix. You may have noticed that the `crate-type` is an array, not a single string. The reason for that is that our project can emit several -targets - in particular, we can add there the default `"rlib"` crate type to -make it generate a "rust library" output - which is what we need to use as a +targets - in particular, we can add the default `"rlib"` crate type to it. This +makes it generate a "rust library" output which is what we need to be able to use it as a dependency. Let's update our `Cargo.toml`: ```toml @@ -96,26 +96,24 @@ crate-type = ["cdylib", "rlib"] # cw-multi-test = "0.15.1" ``` -Also, note I changed the contract name - "contract" is not very descriptive, so -I updated it to "admin". +Also note the change of contract name - "contract" is not very descriptive, so +we've updated it to "admin". ## Project structure -Last but not least - we want to better structure our project. So far, we have -only one contract, so we just worked on it as a whole project. Now we want some -directory tree that reflects relations between contracts we create. +Last but not least, we want to improve the structure of our project. Up to now we only had +one contract to deal with, which is why we just worked on it as the whole project. Now we want to create a +directory tree that reflects relations between the contracts we create. -First, create a directory for the project. Then we want to create a "contracts" -subdirectory in it. It is not technically required from Rust's POV, but there -are tools in our environment, like the workspace optimizer, which would assume -it is where it should look for a contract. It is a common pattern you will see -in CosmWasm contracts repos. +First, let's create a directory for the project. Then we'll want to create a "contracts" +subdirectory inside it. This is not technically required from Rust's point-of-view, but there +are tools in our environment such as the workspace optimizer which will look for contracts there. This is a common pattern you will see in CosmWasm contracts repos. -Then we copy the whole project directory from the previous chapter into the -`contracts`, renaming it to `admin`. +Next we copy the whole project directory from the previous chapter into the +`contracts` folder, renaming it to `admin`. -Finally, we want to couple all our projects (for now, it is just one, but we know -there will be more there). To do so, we create the workspace-level `Cargo.toml` +Finally, we want to group all our projects (for now, we have just one, but we know +there will be more). To do so, we create a workspace-level `Cargo.toml` file in the top-level project directory: ```toml @@ -125,18 +123,18 @@ resolver = "2" ``` This `Cargo.toml` differs slightly from the typical project-level one - it -defines the workspace. The most important field here is the `members` - it -defines projects being part of the workspace. +defines a workspace. The most important field here is `members` - it +defines projects as being part of the workspace. -The other field is the `resolver`. It is something to remember to add - it -instructs cargo to use version 2 of the dependency resolver. This has been the -default for non-workspaces since Rust 2021, but because of compatibility reasons, -the default couldn't be changed for workspaces - but it is advised to add it to -every single newly created workspace. +The other field is the `resolver`. This is something it is important to remember to add - it +instructs Cargo to use version 2 of the dependency resolver. This has been the +default for non-workspaces since Rust 2021, but for compatibility reasons +the default couldn't be changed for workspaces. It is generally recommended to add it to +every newly created workspace. -The last field which might be useful for workspaces is exclude - it allows to -create projects in the workspace directory tree, which is not a part of this -workspace - we will not use it, but it is good to know about it. +The last field which might be useful for some workspaces is exclude - it allows us to +create projects in the workspace directory tree which are not a part of this +workspace. We won't use it, but it can be useful to know about it. Now just for clarity, let's see the top-level directory structure: @@ -151,7 +149,7 @@ Now just for clarity, let's see the top-level directory structure: └── debug ``` -You can see the target directory and `Cargo.lock` files existing in the tree - it is -because I already built and ran tests for the `admin` contract - in Rust workspaces, -`cargo`` knows to build everything in the top level, even if it would be built from -the inner directory. +You can see the target directory and `Cargo.lock` files exist in the tree - this is +because we already built and ran tests for the `admin` contract. In Rust workspaces, +Cargo knows to build everything at the top level even if it was initially built from +an inner directory. diff --git a/src/cross-contract/map-storage.md b/src/cross-contract/map-storage.md index ef8ff15..f197868 100644 --- a/src/cross-contract/map-storage.md +++ b/src/cross-contract/map-storage.md @@ -1,6 +1,6 @@ # Map storage -There is one thing to be immediately improved in the admin contract. Let's +There is one thing we can immediately improve in the admin contract. Let's check the contract state: ```rust @@ -11,22 +11,19 @@ pub const ADMINS: Item> = Item::new("admins"); pub const DONATION_DENOM: Item = Item::new("donation_denom"); ``` -Note that we keep our admin list as a single vector. However, in the whole -contract, in most cases, we access only a single element of this vector. - -This is not ideal, as now, whenever we want to access the single admin entry, -we have first to deserialize the list containing all of them and then iterate -over them until we find the interesting one. This might consume a serious -amount of gas and is completely unnecessary overhead - we can avoid that using +Note that we keep our admin list as a single vector. However, in most cases we only need to access a single element of this vector. This is not ideal since it means that whenever we want to access a single admin entry, +we first have to deserialize the entire list of them and then iterate +over them until we find the one we are interested in. This may consume a serious +amount of gas and is completely unnecessary overhead that we can avoid by using the [Map](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html) storage accessor. -## The `Map` storage +## The `Map` storage accessor -First, let's define a map - in this context, it would be a set of keys with values +First, let's define a map - in this context, it will be a set of keys with values assigned to them, just like a `HashMap` in Rust or dictionaries in many languages. -We define it as similar to an `Item`, but this time we need two types - the key type -and the value type: +We define it in a similar way to how we define an `Item`, but this time we need two types, a key type +and a value type: ```rust use cw_storage_plus::Map; @@ -34,10 +31,10 @@ use cw_storage_plus::Map; pub const STR_TO_INT_MAP: Map = Map::new("str_to_int_map"); ``` -Then to store some items on the [`Map`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html), -we use a +To store some items on the [`Map`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html), +we use the [`save`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.save) -method - same as for an `Item`: +method just as we did for an `Item`: ```rust STR_TO_INT_MAP.save(deps.storage, "ten".to_owned(), 10); @@ -54,15 +51,15 @@ let two = STR_TO_INT_MAP.may_load(deps.storage, "two".to_owned())?; assert_eq!(two, None); ``` -Obviously, if the element is missing in the map, the +Obviously, if the element is missing from the map, the [`load`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.load) -function will result in an error - just like for an item. On the other hand - +function will result in an error - just as for an item. Alternatively we can use [`may_load`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.may_load) -returns a `Some` variant when element exits. +which returns a `Some` variant when an element exits. Another very useful accessor that is specific to the map is the [`has`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.has) -function, which checks for the existence of the key in the map: +function, which checks for the existence of a key in the map: ```rust let contains = STR_TO_INT_MAP.has(deps.storage, "three".to_owned())?; @@ -84,53 +81,53 @@ for item in STR_TO_INT_MAP.range(deps.storage, None, None, Order::Ascending) { } ``` -First, you might wonder about extra values passed to +You may be wondering about those extra values we passed to [`keys`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.keys) and [`range`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.range) - -those are in order: lower and higher bounds of iterated elements, and the order -elements should be traversed. +in order, these are: lower and higher bounds of iterated elements, and the order +in which elements should be traversed. -While working with typical Rust iterators, you would probably first create an +When working with typical Rust iterators, you would probably first create an iterator over all the elements and then somehow skip those you are not -interested in. After that, you will stop after the last interesting element. +interested in. After that, you stop after the last interesting element. -It would more often than not require accessing elements you filter out, and -this is the problem - it requires reading the element from the storage. And -reading it from the storage is the expensive part of working with data, which -we try to avoid as much as possible. One way to do it is to instruct the Map -where to start and stop deserializing elements from storage so it never reaches +This would more often than not require accessing elements you filter out, and +this is the problem - it requires reading that element from the storage. Reading from the storage is the expensive part of working with data, which +we try to avoid as much as possible. One way to do that is to instruct the Map +where exactly to start and stop deserializing elements from storage so it never reaches those outside the range. -Another critical thing to notice is that the iterator returned by both keys and -range functions are not iterators over elements - they are iterators over `Result`s. -It is a thing because, as it is rare, it might be that item is supposed to exist, +Another critical thing to notice is that the iterators returned by both keys and +range functions are not actually iterators over elements - they are iterators over `Result`s. +Although it is rare, it's possible that an item is supposed to exist but there is some error while reading from storage - maybe the stored value is -serialized in a way we didn't expect, and deserialization fails. This is actually -a real thing that happened in one of the contracts I worked on in the past - we +serialized in a way we didn't expect and deserialization fails. This is actually +a real thing that happened in one of the contracts the authors worked on in the past - we changed the value type of the Map, and then forgot to migrate it, which caused all sorts of problems. ## Maps as sets -So I imagine you can call me crazy right now - why do I spam about a `Map`, while -we are working with vector? It is clear that those two represent two distinct +So you may well be thinking we're crazy right now - why are we going on about `Map` so much when +we are actually working with a vector? They clearly represent two distinct things! Or do they? Let's reconsider what we keep in the `ADMINS` vector - we have a list of objects -which we expect to be unique, which is a definition of a mathematical set. So -now let me bring back my initial definition of the map: +that we expect to be unique, which is the definition of a mathematical set. So +now let us bring back our initial definition of a map: + +> First, let's define a map - in this context, it will be a *set* of keys with values +> assigned to them, just like a `HashMap` in Rust or dictionaries in many languages. -> First, let's define a map - in this context, it would be a *set* of keys with -> values assigned to them, just like a HashMap in Rust or dictionaries in many languages. -I purposely used the word "set" here - the map has the set built into it. It is -a generalization of a set or reversing the logic - the set is a particular case -of a map. If you imagine a set that map every single key to the same value, then +We purposely used the word "set" here - a map has a set built into it. It is +a generalization of a set, or reversing the logic, a set is a particular case +of a map. If you imagine a set that maps every single key to the same value, then the values become irrelevant, and such a map becomes a set semantically. -How can you make a map mapping all the keys to the same value? We pick a type -with a single value. Typically in Rust, it would be a unit type (`()`), but in +How can we make a map that maps all the keys to the same value? We pick a type +with a single value. Typically in Rust, this would be a unit type (`()`), but in CosmWasm, we tend to use the [`Empty`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.Empty.html) type from CW standard crate: @@ -168,8 +165,8 @@ pub fn instantiate( } ``` -It didn't simplify much, but we no longer need to collect our address. Then -let's move to the leaving logic: +It didn't simplify much, but we no longer need to collect our addresses. Then +let's move on to the leaving logic: ```rust use crate::state::ADMINS; @@ -186,20 +183,20 @@ pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { } ``` -Here we see a difference - we don't need to load a whole vector. We remove a +Here we notice the difference - we don't need to load a whole vector. We remove a single entry with the [`remove`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.remove) function. -What I didn't emphasize before, and what is relevant, is that `Map` stores every +Something relevant that we didn't point out before is that `Map` stores every single key as a distinct item. This way, accessing a single element will be cheaper than using a vector. -However, this has its downside - accessing all the elements is more -gas-consuming using Map! In general, we tend to avoid such situations - the +However, this has its downside as well - accessing all the elements consumes more +gas using Map! In general, we tend to try to avoid such situations - the linear complexity of the contract might lead to very expensive executions -(gas-wise) and potential vulnerabilities - if the user finds a way to create -many dummy elements in such a vector, he may make the execution cost exceeding +(gas-wise) and potential vulnerabilities. If the user finds a way to create +many dummy elements in such a vector they can make the execution cost exceed any gas limit. Unfortunately, we have such an iteration in our contract - the distribution flow @@ -238,13 +235,13 @@ pub fn donate(deps: DepsMut, info: MessageInfo) -> Result>` here. -Hopefully, it is not the case - the distribution does not have to be linear in -complexity! It might sound a bit crazy, as we have to iterate over all receivers -to distribute funds, but this is not true - there is a pretty nice way to do so -in constant time, which I will describe later in the book. For now, we will -leave it as it is, acknowledging the flaw of the contract, which we will fix later. +If writing a contract like this in which this `donate` is a critical common part of the +flow, we would advise going for an `Item>` here. +Thankfully, this is not the case - the distribution does not have to be linear in +complexity! It might sound a bit unbelievable, since we have to iterate over all receivers +to distribute funds, but there is a pretty nice way to do so +in constant time which I will describe later in the book. For now, we will +leave it as it is, just acknowledging this flaw in the contract that we will fix later. The final function to fix is the `admins_list` query handler: @@ -264,21 +261,21 @@ pub fn admins_list(deps: Deps) -> StdResult { Here we also have an issue with linear complexity, but it is far less of a problem. -First, queries are often purposed to be called on local nodes, with no gas cost - +First of all, queries are often purposed to be called on local nodes, with no gas cost. This means that we can query contracts as much as we want. -And then, even if we have some limit on execution time/cost, there is no reason to +Even if we do have some limit on execution time/cost, there is no reason to query all the items every single time! We will fix this function later, adding -pagination - to limit the execution time/cost of the query caller would be able to -ask for a limited amount of items starting from the given one. Knowing this chapter, +pagination - to limit the execution time/cost of the query caller by being able to +ask for a limited amount of items starting from a given one. Having gone through this chapter, you can probably figure implementation of it right now, but I will show the common -way we do that when I go through common CosmWasm practices. +way we do it when we get to looking at common CosmWasm practices. ## Reference keys -There is one subtlety to improve in our map usage. +There is another subtlety to improve in our map usage. -The thing is that right now, we index the map with the owned Addr key. That forces +The thing is that right now, we index the map with the owned Addr key. This forces us to clone it if we want to reuse the key (particularly in the leave implementation). This is not a huge cost, but we can avoid it - we can define the key of the map to be a reference: @@ -291,7 +288,7 @@ pub const ADMINS: Map<&Addr, Empty> = Map::new("admins"); pub const DONATION_DENOM: Item = Item::new("donation_denom"); ``` -Finally, we need to fix the usages of the map in two places: +Finally, we need to fix the usage of the map in two places: ```rust # use crate::state::{ADMINS, DONATION_DENOM}; diff --git a/src/cross-contract/working-with-time.md b/src/cross-contract/working-with-time.md index 0457e83..dfa46ba 100644 --- a/src/cross-contract/working-with-time.md +++ b/src/cross-contract/working-with-time.md @@ -2,15 +2,15 @@ The concept of time in the blockchain is tricky - as in every distributed system, it is not easy to synchronize the -clocks of all the nodes. +clocks of all of the nodes. -However, there is the notion of a time that is even -monotonic - which means that it should never go "backward" -between executions. Also, what is important is - time is -always unique throughout the whole transaction - and even -the entire block, which is built of multiple transactions. +There is a notion of a time that it is should be +monotonic, meaning that it should never go "backwards" +between executions. Also, it is important that time is +always unique throughout not just the whole transaction, but also +the entire block, which of course can be built up from multiple transactions. -The time is encoded in the +Time is encoded in the [`Env`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.Env.html) type in its [`block`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.BlockInfo.html) @@ -26,16 +26,14 @@ pub struct BlockInfo { You can see the `time` field, which is the timestamp of the processed block. The `height` field is also worth -mentioning - it contains a sequence number of the processed -block. It is sometimes more useful than time, as it is -guaranteed that the `height` field is guaranteed to increase +mentioning as it contains a sequence number of the processed +block. It is sometimes more useful than time, as the `height` field is guaranteed to increase between blocks, while two blocks may be executed with the -same `time` (even though it is rather not probable). +same `time` (even though this is rather improbable). -Also, many transactions might be executed in a single block. -That means that if we need a unique id for the execution of -a particular message, we should look for something more. -This thing is a +Since many transactions may be executed in a single block, if we need a unique id for the execution of +a particular message then we shall need something else. +What we can use is the [`transaction`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.TransactionInfo.html) field of the `Env` type: @@ -46,14 +44,14 @@ pub struct TransactionInfo { ``` The `index` here contains a unique index of the transaction -in the block. That means that to get the unique identifier -of a transaction through the whole block, we can use the +in the block. That means that to get a unique identifier +of a transaction within the entire blockchain, we can use the `(height, transaction_index)` pair. ## Join time We want to use the time in our system to keep track of the -join time of admins. We don't yet add new members to the +join time of admins. We won't yet solve this for adding new members to the group, but we can already set the join time of initial admins. Let's start updating our state: @@ -66,20 +64,16 @@ pub const ADMINS: Map<&Addr, Timestamp> = Map::new("admins"); # pub const DONATION_DENOM: Item = Item::new("donation_denom"); ``` -As you can see, our admins set became a proper map - we will +As you can see, our admins set has became a proper map - we will assign the join time to every admin. -Now we need to update how we initialize a map - we stored the Empty data previously, but it nevermore matches our value type. Let's check an updated instantiation function: +Now we need to update how we initialize a map - we stored the `Empty` data previously, but it no longer matches our value type. Let's consider an updated instantiation function: -You might argue to create a separate structure for the value -of this map, so in the future, if we would need to add -something there, but in my opinion, it would be premature - -we can also change the entire value type in the future, as -it would be the same breaking change. +You might argue for creating a separate structure for the value +of this map, so that in future we can add things if necessary. In the author's opinion, this would be premature as +it would be the same breaking change, and in any case we can still change the entire value type in the futur if we need to. -Now we need to update how we initialize a map - we stored -the `Empty` data previously, but it nevermore matches our -value type. Let's check an updated instantiation function: +Let's take a look at an updated instantiation function: ```rust use crate::state::{ADMINS, DONATION_DENOM}; @@ -105,9 +99,9 @@ pub fn instantiate( Instead of storing `&Empty {}` as an admin value, we store the join time, which we read from `&env.block.time`. Also, -note that I removed the underscore from the name of the -`env` block - it was there only to ensure the Rust compiler -the variable is purposely unused and not some kind of a bug. +note that we have removed the underscore from the name of the +`env` block (it was there only to assure the Rust compiler that +the variable was deliberately unused and not some kind of bug). Finally, remember to remove any obsolete `Empty` imports through the project - the compiler should help you point out @@ -117,8 +111,7 @@ unused imports. The last thing to add regarding join time is the new query asking for the join time of a particular admin. Everything -you need to do that was already discussed, I'll leave it for -you as an exercise. The query variant should look like: +you need to do that was already discussed, so it is left as an exercise. The query variant should look like this: ```rust #[returns(JoinTimeResp)] @@ -134,23 +127,23 @@ pub struct JoinTimeResp { } ``` -You may question that in response type, I suggest always returning a `joined` -value, but what to do when no such admin is added? Well, in such a case, I -would rely on the fact that `load` function returns a descriptive error of -missing value in storage - however, feel free to define your own error for such -a case or even make the `joined` field optional, and be returned if requested +You may question the fact that the suggested response type always returns a `joined` +value, while there is always the possibility that no such admin exists in the group. Well, in such a case, we +rely on the fact that the `load` function returns a descriptive error of +`missing value in storage`. However, feel free to define your own error for such +a case or even to make the `joined` field optional, to be returned only if the requested admin exists. -Finally, there would be a good idea to make a test for new functionality - call -a new query right after instantiation to verify initial admins has proper join +Finally, it would be a good idea to make a test for this new functionality - call +a new query right after instantiation to verify that the initial admins all have a proper join time (possibly by extending the existing instantiation test). -One thing you might need help with in tests might be how to get the time of -execution. Using any OS-time would be doomed to fail - instead, you can call +One thing you might need help with in the tests is how exactly to get the time of +execution. Using any OS-time will be doomed to fail - instead, you can call the [`block_info`](https://docs.rs/cw-multi-test/0.16.4/cw_multi_test/struct.App.html#method.block_infohttps://docs.rs/cw-multi-test/0.16.4/cw_multi_test/struct.App.html#method.block_info) function to reach the [`BlockInfo`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.BlockInfo.html) structure containing the block state at a particular moment in the app - calling -it just before instantiation would make you sure you are working with the same state +it just before instantiation ensures that you are working with the same state which would be simulated on the call. diff --git a/src/setting-up-env.md b/src/setting-up-env.md index a3d35b2..0871b64 100644 --- a/src/setting-up-env.md +++ b/src/setting-up-env.md @@ -1,25 +1,25 @@ # Setting up the environment -To work with CosmWasm smart contract, you will need rust installed on your -machine. If you don't have one, you can find installation instructions on [the +To work with a CosmWasm smart contract, you will need Rust installed on your +machine. If you don't have it, you can find installation instructions on [the Rust website](https://www.rust-lang.org/tools/install). -I assume you are working with a stable Rust channel in this book. +I assume you are working with a stable Rust version in this book. -Additionally, you will need the Wasm rust compiler backend installed to build +Additionally, you will need the Wasm Rust compiler backend installed to build Wasm binaries. To install it, run: ``` rustup target add wasm32-unknown-unknown ``` -Optionally if you want to try out your contracts on a testnet, you will need a -[wasmd](https://github.com/CosmWasm/wasmd) binary. We would focus on testing -contracts with Rust unit testing utility throughout the book, so it is not -required to follow. However, seeing the product working in a real-world -environment may be nice. +Optionally, if you also want to try out your contracts on a testnet then you will need the +[wasmd](https://github.com/CosmWasm/wasmd) binary. We will focus on testing +contracts using Rust's unit testing utility throughout the book, so it is not +required to follow along. However, seeing the product working in a real-world +environment may be nice! -To install `wasmd`, first install the [golang](https://github.com/golang/go/wiki#working-with-go). Then +To install `wasmd`, first install [golang](https://github.com/golang/go/wiki#working-with-go). Then clone the `wasmd` and install it: ``` @@ -28,14 +28,14 @@ $ cd ./wasmd $ make install ``` -Also, to be able to upload Rust Wasm Contracts into the blockchain, you will need +Also, to be able to upload Rust Wasm Contracts onto the blockchain, you will need to install [docker](https://www.docker.com/). To minimize your contract sizes, -it will be required to run CosmWasm Rust Optimizer; without that, more complex +it will be required to run CosmWasm Rust Optimizer, otherwise more complex contracts might exceed a size limit. ## cosmwasm-check utility -An additional helpful tool for building smart contracts is the `cosmwasm-check`[utility](https://github.com/CosmWasm/cosmwasm/tree/main/packages/check). It allows you to check if the wasm binary is a proper smart contract ready to upload into the blockchain. You can install it using cargo: +An additional helpful tool for building smart contracts is the `cosmwasm-check`[utility](https://github.com/CosmWasm/cosmwasm/tree/main/packages/check). It allows you to check if the wasm binary is a proper smart contract ready to upload onto the blockchain. You can install it using Cargo: ``` $ cargo install cosmwasm-check @@ -50,7 +50,7 @@ Contract checking 1.2.3 ## Verifying the installation -To guarantee you are ready to build your smart contracts, you need to make sure you can build examples. +To guarantee you are ready to build your smart contracts, you need to make sure you can build the examples. Checkout the [cw-plus](https://github.com/CosmWasm/cw-plus) repository and run the testing command in its folder: @@ -60,12 +60,12 @@ $ cd ./cw-plus cw-plus $ cargo test ``` -You should see that everything in the repository gets compiled, and all tests pass. +You should see that everything in the repository gets compiled correctly, and all tests pass. -`cw-plus` is a great place to find example contracts - look for them in `contracts` directory. The +`cw-plus` is a great place to find example contracts - look for them in the `contracts` directory. The repository is maintained by CosmWasm creators, so contracts in there should follow good practices. -To verify the `cosmwasm-check` utility, first, you need to build a smart contract. Go to some contract directory, for example, `contracts/cw1-whitelist`, and call `cargo wasm`: +To verify the `cosmwasm-check` utility, you will first need to build a smart contract. Go to some contract's directory (for example, `contracts/cw1-whitelist`), and call `cargo wasm`: ``` cw-plus $ cd contracts/cw1-whitelist diff --git a/src/wasmd-quick-start.md b/src/wasmd-quick-start.md index a629cb3..eebaff6 100644 --- a/src/wasmd-quick-start.md +++ b/src/wasmd-quick-start.md @@ -1,8 +1,8 @@ # Quick start with `wasmd` -In the past, we suggested playing with contracts on the `malaga` testnet using `wasmd`. -Now `malaga` is no longer operative, and the best way to test the contract in the -real environment is to use one of the big CosmWasm chains testnets - Osmosis, Juno, -Terra, or other ones. +We used to suggest playing with contracts on the `malaga` testnet using `wasmd`. +Since `malaga` is no longer operational, currently the best way to test the contract in a +real environment is to use one of the big CosmWasm chains testnets: Osmosis, Juno, +Terra, etc. [Here](https://docs.osmosis.zone/cosmwasm/testnet/cosmwasm-deployment/) is the -recommended introduction on how to start with the Osmosis testnet. +introduction we recommend which explains how to start with the Osmosis testnet.