From c5b39a622c149203affac3e8c317a3516a9239d9 Mon Sep 17 00:00:00 2001 From: tiffgerst Date: Sat, 14 Sep 2024 19:03:52 +0100 Subject: [PATCH 1/3] fix(book): update dependecies, fix code accordingly and fix other minor issues --- examples/03-basics/Cargo.toml | 16 +-- examples/03-basics/src/contract.rs | 6 +- .../contracts/admin/src/contract.rs | 6 +- src/actor-model.md | 4 +- src/actor-model/actors-in-blockchain.md | 83 +++++------ src/actor-model/contract-as-actor.md | 80 +++++------ src/actor-model/idea.md | 129 ++++++++---------- src/basics/building-contract.md | 2 +- src/basics/events.md | 31 +++-- src/basics/execute.md | 29 ++-- src/basics/fp-types.md | 4 +- src/basics/funds.md | 95 ++++++------- src/basics/good-practices.md | 27 ++-- src/basics/multitest-intro.md | 10 +- src/basics/query-testing.md | 12 +- src/basics/query.md | 24 ++-- src/basics/rust-project.md | 2 +- src/basics/state.md | 64 ++++++--- src/cross-contract/design.md | 23 ++-- src/cross-contract/fixing-admin.md | 41 +----- src/cross-contract/map-storage.md | 53 +++---- src/cross-contract/working-with-time.md | 52 ++----- src/wasmd-quick-start.md | 1 + 23 files changed, 370 insertions(+), 424 deletions(-) diff --git a/examples/03-basics/Cargo.toml b/examples/03-basics/Cargo.toml index 0089f1d..f9a9ee0 100644 --- a/examples/03-basics/Cargo.toml +++ b/examples/03-basics/Cargo.toml @@ -10,13 +10,13 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -cosmwasm-std = { version = "1.1.4", features = ["staking"] } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw-storage-plus = "0.15.1" -thiserror = "1" -schemars = "0.8.1" -cw-utils = "0.15.1" -cosmwasm-schema = "1.1.4" +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +cw-storage-plus = "2.0.0" +schemars = "0.8.16" +thiserror = { version = "1.0.58" } +cw-utils = "2.0.0" +cosmwasm-schema = "2.1.3" [dev-dependencies] -cw-multi-test = "0.15.1" +cw-multi-test = "2.1.1" diff --git a/examples/03-basics/src/contract.rs b/examples/03-basics/src/contract.rs index e4ea4c1..bf8a781 100644 --- a/examples/03-basics/src/contract.rs +++ b/examples/03-basics/src/contract.rs @@ -2,7 +2,7 @@ use crate::error::ContractError; use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; use crate::state::{ADMINS, DONATION_DENOM}; use cosmwasm_std::{ - coins, to_binary, BankMsg, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, + coins, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, }; pub fn instantiate( @@ -26,8 +26,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; match msg { - Greet {} => to_binary(&query::greet()?), - AdminsList {} => to_binary(&query::admins_list(deps)?), + Greet {} => to_json_binary(&query::greet()?), + AdminsList {} => to_json_binary(&query::admins_list(deps)?), } } diff --git a/examples/05-cross-contract/contracts/admin/src/contract.rs b/examples/05-cross-contract/contracts/admin/src/contract.rs index cc64c64..3bdf65f 100644 --- a/examples/05-cross-contract/contracts/admin/src/contract.rs +++ b/examples/05-cross-contract/contracts/admin/src/contract.rs @@ -2,7 +2,7 @@ use crate::error::ContractError; use crate::msg::{AdminsListResp, ExecuteMsg, InstantiateMsg, JoinTimeResp, QueryMsg}; use crate::state::{ADMINS, DONATION_DENOM}; use cosmwasm_std::{ - coins, to_binary, Addr, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, + coins, to_json_binary, Addr, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; @@ -25,8 +25,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; match msg { - AdminsList {} => to_binary(&query::admins_list(deps)?), - JoinTime { admin } => to_binary(&query::join_time(deps, admin)?), + AdminsList {} => to_json_binary(&query::admins_list(deps)?), + JoinTime { admin } => to_json_binary(&query::join_time(deps, admin)?), } } diff --git a/src/actor-model.md b/src/actor-model.md index ce78331..c33a210 100644 --- a/src/actor-model.md +++ b/src/actor-model.md @@ -1,9 +1,9 @@ # Actor model -This section describes the fundaments of CosmWasm smart contracts architecture, which determines how do they communicate +This section describes the fundamentals of CosmWasm smart contracts architecture, which determines how 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. -The whole thing described here is officially documented in the +Everything 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..9f14101 100644 --- a/src/actor-model/actors-in-blockchain.md +++ b/src/actor-model/actors-in-blockchain.md @@ -1,60 +1,59 @@ # 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. +blockchain-specific terms. However, before we can dive into the code, we need +to establish some common language. To do so we need to look at contracts from +the perspective of external users instead of their implementation. -In this part, I would use the `wasmd` binary to communicate with the malaga +In this part, I will use the `wasmd` binary to communicate with a testnet. To properly set it up, check the [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 kind of like starting from the end, I would start with the state part of +the actor model. In regards to traditional systems, the blockchain is most like a database. Going back to 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! +manifest work performed to the world. But what is the thing whose purpose is to +keep the state? Its 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 +mechanisms" are crucial for BC's existence and they are the reasons we 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) +But you can say: what about the financial part?! Isn't a 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 +with a mapping of addresses 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 +it you just send a message to a special built-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 +But if the 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 +again - to operate on this table, you use a "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 +Now there remains the question - why do I even care about BC being a DB? The reason +is that it makes reasoning about everything in blockchain much more 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 migrations in CosmWasm are very much equivalents of schema migrations in traditional databases. -So, the thing to remember - blockchain is very similar to a database, having some +So, the thing to remember - a blockchain is very similar to a database, having some specially reserved tables (like native tokens, code repository), 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 in the whole blockchain, but it can only modify its own bucket/the bucket of the contracts it has created. ## Compile the contract @@ -67,26 +66,28 @@ now, so we will start with compiling it. Start with cloning the repository: $ git clone git@github.com:CosmWasm/cw-plus.git ``` -Then go to `cw4-group` contract and build it: +Then build the contracts. You may want to comment out the other contracts/update members in the Cargo.toml file for faster execution: ```bash -$ cd cw-plus/contracts/cw4-group +$ cd cw-plus $ docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.6 + cosmwasm/workspace-optimizer:0.16.0 ``` - +If you are running on an ARM architecture, be sure to specify a compatible platform i.e. --platform linux/amd64 Your final binary should be located in the `cw-plus/artifacts` folder (`cw-plus` being where you cloned your repository). ## 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 and have started the wasmd node): ```bash -$ wasmd tx wasm store ./cw4-group.wasm --from wallet $TXFLAG -y -b block +$ wasmd keys add wallet --keyring-backend=test +$ wasmd genesis add-genesis-account $(wasmd keys show wallet -a --keyring-backend=test) 1000000000stake --keyring-backend=test +$ wasmd tx wasm store ./artifacts/cw4_group.wasm --from wallet --gas 1500000 --fees 75000stake --chain-id=docs-chain-1 --keyring-backend=test -y ``` As a result of such an operation you would get json output like this: @@ -103,12 +104,13 @@ 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 +I 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 the 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: +the id of `1069`. I can now look at the code by querying for it. If the code id is not shown, you can optionally query for it as shown below. ```bash +$ wasmd query wasm list-code $ wasmd query wasm code 1069 code.wasm ``` @@ -127,7 +129,8 @@ instantiation is calling a constructor. To do that, I would send an instantiate message to my contract: ```bash -$ wasmd tx wasm instantiate 1069 '{"members": []}' --from wallet --label "Group 1" --no-admin $TXFLAG -y +$ wasmd tx wasm instantiate 1069 '{"members": []}' --from wallet --label "Group 1" --no-admin --gas 1500000 --fees 75000stake --chain-id=docs-chain-1 --keyring-backend=test -y +$ wasmd query tx F1E9ABDC201F794041A096B17F3F1CDA6B8A06443F5174C01F63A191528F2F3B ``` What I do here is create a new contract and immediately call the `Instantiate` @@ -159,15 +162,15 @@ logs: ``` 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. +interesting event and extracting information from it. 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 +it is a way to notify the world that something has 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 +announcing an event, so you know some summary of operation and can go and do something useful with it. -So, what use can we do with the contract? We obviously can call it! But first +So, what can we do with the contract? We obviously can call it! But first I want to tell you about addresses. ## Addresses in CosmWasm @@ -206,7 +209,7 @@ $ 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. +this sender is an address that would pay a gas cost. ## Querying the contract @@ -253,7 +256,8 @@ $ wasmd keys show wallet And instantiate a new group contract - this time with proper admin: ```bash -$ wasmd tx wasm instantiate 1069 '{"members": [], "admin": "wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx"}' --from wallet --label "Group 1" --no-admin $TXFLAG -y +$ wasmd tx wasm instantiate 1069 '{"members": [], "admin": "wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx"}' --from wallet --label "Group 1" --no-admin --gas 1500000 --fees 75000stake --chain-id=docs-chain-1 --keyring-backend=test -y +$ wasmd query tx 4F0A116BBBA6EF3C060465E788CB795C39AEBB1F0BA8A4013B3CAC536F36B425 .. logs: - events: @@ -268,9 +272,7 @@ logs: You may ask, why do we pass some kind of `--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 +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, 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 the contract. @@ -295,8 +297,7 @@ 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? ```bash -wasmd tx wasm execute wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "update_members": { "add": [{ "addr": "wasm1um59mldkdj8ayl5gkn -p9pnrdlw33v40sh5l4nx", "weight": 1 }], "remove": [] } }' --from wallet $TXFLAG -y +wasmd tx wasm execute wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "update_members": { "add": [{ "addr": "wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx", "weight": 1 }], "remove": [] } }' --from wallet --gas 1500000 --fees 75000stake --chain-id=docs-chain-1 --keyring-backend=test -y ``` The message for modifying the members is `update_members` and it has two diff --git a/src/actor-model/contract-as-actor.md b/src/actor-model/contract-as-actor.md index 2cb8b97..3be4979 100644 --- a/src/actor-model/contract-as-actor.md +++ b/src/actor-model/contract-as-actor.md @@ -6,7 +6,7 @@ structure to understand how different features of the actor model are mapped 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 +for the series itself. Instead I'll go through contract elements to roughly visualize how to handle architecture in the actor model. ## The state @@ -30,7 +30,7 @@ As you may already figure out, we want to check the `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 +of the contract state - as tables in a databases. The types of those constants represent what kind of table this is. The most basic ones are `Item`, which keeps zero or one element of a given type, and `Map` which is a key-value map. @@ -57,18 +57,18 @@ Other types of storage objects not used in group contracts are: 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 +Do you remember that I told you before that the 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. -You may ask - why all that data for a contract are not auto-fetched by +You may ask - why is all that data for a contract not auto-fetched by whatever is running it. That is a good question. The reason is that we want 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 +about gas at all, but that was only partially true. You don't need to know +exactly how gas is calculated, but by lowering your gas cost, the 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 particular call. @@ -94,7 +94,7 @@ Note, that all the messages are attributed with `#[derive(Serialize, Deserialize)]`, and `#[serde(rename_all="snake_case")]`. Those attributes come from the [serde](https://serde.rs/) crate, and they help us with -deserialization of them (and serialization in case of sending +deserialization (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 @@ -105,19 +105,19 @@ like 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, -and it is related to how `serde` serializes enum. +more Rusty `Admin`. This is on purpose, to make JSONs cleaner, +and it is related to how `serde` serializes enums. 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 +in mind, to keep your message names 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,12 +147,12 @@ 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 +a message is to be handled by a contract. You can think of them as the `main` function of normal programs, except they have a signature that better describes the blockchain itself. -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 +What is very important is that the names of these entry points (similarly to +the `main` function) are fixed - this is so the virtual machine knows exactly what to call. So, let's start with the first line. Every entry point is attributed with @@ -173,13 +173,13 @@ entry points for us, and those entry points are just calling our functions. 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: +additional arguments 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 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. + only allows view operations. * `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. @@ -203,12 +203,12 @@ contract. The important thing is 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 +The common way to create it is just calling a `to_json_binary` method on an object implementing `serde::Serialize`, and they 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 +point - the `cosmwasm_std::Response` type. This one keeps everything needed to complete contract execution. There are three chunks of information in that. @@ -219,13 +219,13 @@ 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 +only a single event, and to make it a bit easier to operate on, 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 `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 +to 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 @@ -235,23 +235,23 @@ all the messages sent by it would also be handled (even if they failed). So, when all the sub-messages sent by 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 +the `data`. 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 completely different way of emitting events and data. Any attributes emitted by -the contract would be visible on blockchain eventually (unless the whole +the contract would be visible on the 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 +being a 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. +I will explain this in a bit more detail later on. ## 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 +directly from the documentation, but I want to take a closer look at the part about sending messages. The first function to use here is `add_message`, which takes as an argument the @@ -262,7 +262,7 @@ 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 +`gas_limit` thing, but this is not so important - it just 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 @@ -288,20 +288,20 @@ you would typically use helper constructors: `SubMsg::reply_on_success`, ## CosmosMsg -If you took a look at the `CosmosMsg` type, you could be very surprised - there +If you take a look at the `CosmosMsg` type, you might be very surprised - there are so many variants of them, 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 +This one is very 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). +migrations (I will tell you a bit about them 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 +equivalent to transferring them to some black hole 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. @@ -330,7 +330,7 @@ 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 `into_result` function. -In the error case of `ContracResult`, there is a string - as I mentioned +In the error case of `ContractResult`, there is a string - as I 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 +As I said before, you never need to worry about forwarding events - CosmWasm +will do it anyway. The `data` however, is another story. As mentioned before, +every call will 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 +by the top-level message. But this 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 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 @@ -365,15 +365,15 @@ be used. 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 +release an updated version of applications. This is also the case in the blockchain - +SmartContracts 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. 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, +after migration). 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 @@ -411,7 +411,7 @@ 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 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 +will not cover them, because it is not related to `CosmWasm` itself. The signature of `sudo` looks like this: ```rust diff --git a/src/actor-model/idea.md b/src/actor-model/idea.md index 3a5dd65..12ced9b 100644 --- a/src/actor-model/idea.md +++ b/src/actor-model/idea.md @@ -2,7 +2,7 @@ The actor model is the solution to the problem of communication between smart contracts. Let's take a look at the reasons why this particular solution is -chosen in CosmWasm, and what are the consequences of that. +chosen in CosmWasm, and what the consequences of that are. ## The problem @@ -21,7 +21,7 @@ of problems, in particular with shared state consistency. The other approach which is far more popular in business-level modeling is to treat entities as actors, which can perform some tasks, but without interrupting it with calls to other contracts. Any calls to other contracts can -only be called after the whole execution is performed. When "subcall" is +only be called after the whole execution is performed. When the "subcall" is finished, it will call the original contract back. This solution may feel unnatural, and it requires different kinds of design @@ -54,7 +54,7 @@ triggered by a chef, but in the Actor model, it is not possible. Here, entities of the system are passive and cannot observe the environment actively - they only react to messages from other system participants. Also in KFC, the seller would not schedule subtasks for particular chefs; instead, he would leave tasks -to be taken by them, when they are free. It is not the case, because as before - +to be taken by them, when they are free. This is not the case, because as before - chefs cannot actively listen to the environment. However, it would be possible to create a contract for being a chef's dispatcher which would collect all orders from sellers, and balance them across chefs for some reason. @@ -64,7 +64,7 @@ orders from sellers, and balance them across chefs for some reason. Actors are the model entities, but to properly communicate with them, we need some kind of protocol. Every actor is capable of performing several actions. In my previous KFC example, the only action seller can do is "Charge payment and -create order". However, it is not always the case - our chefs were proficient +create order". However, this is not always the case - our chefs were proficient at performing both "Prepare fries" and "Prepare Sandwich" actions - and also many more. @@ -83,26 +83,25 @@ the contract itself, plus all the sub-actions it schedules. ## Multi-stage Actions -So as the whole idea makes some sense, there is the problem created by the +So as the whole idea makes more sense, there is the problem created by the actor model: what if I want to perform some action in my contract, but to completely finalize some steps, the contract has to make sure that some -sub-action he scheduled are finished? +sub-action it scheduled are finished? Imagine that in the previous KFC situation, there is no dedicated Waiter. Instead the Seller was serving you a meal when the Chefs finished their job. This kind of pattern is so important and common that in CosmWasm, we developed -a special way to handle it, which is dedicated `Reply` action. +a special way to handle it, which is a dedicated `Reply` action. -So when Seller is scheduling actions for chefs, he assigns some number to this -action (like order id) and passes it to chefs. He also remembers how many -actions he scheduled for every order id. Now every time chef is finished with +So when a Seller is scheduling actions for chefs, he assigns some number to this +action (like order id) and passes it to the chefs. He also remembers how many +actions he scheduled for every order id. Now every time a chef is finished with his action; he would call the special `Reply` action on Seller, in which he would pass back the order id. Then, Seller would decrease the number of actions left for this order, and if it reached zero, he would serve a meal. -Now you can say, that the `Reply` action is completely not needed, as Chefs -could just schedule any arbitrary action on Seller, like `Serve`, why is there +Now you may ask, if Chefs can just schedule any arbitrary action on the Seller, like `Serve`, what is the special `Reply` for? The reason is abstraction and reusability. The Chefs task is to prepare a meal, and that is all. There is no reason for him to know why he is even preparing Fries - if it is part of the bigger task (like order @@ -126,7 +125,7 @@ job would be to show something on the screen, maybe print something. This is not the case with Smart Contracts. The only thing which can be affected by the Smart Contract is their internal state. So, the state is arbitrary data that is kept by the contract. Previously in the KFC example I mentioned, the Seller is -keeping in mind how many actions he scheduled for chefs are not yet finished - +keeping in mind how many actions he scheduled for chefs which have not yet finished - this number is part of the Seller's state. To give a more realistic example of a contract state, let's think about a more @@ -135,19 +134,19 @@ our currency - maybe we want to create some smart contracts-based market for some MMORPG game. So, we need some way to be able to at least transfer currency between players. We can do that, by creating the contract we would call `MmoCurrency`, which would support the `Transfer` action to transfer money to -another player. Then what would be the state of such a contract? It would be +another player. Then what would the state of such a contract be? It would be just a table mapping player names to the amount of currency they own. The -contract we just invited exists in CosmWasm examples, and it is called the +contract we just invented exists in CosmWasm examples, and it is called the [`cw20-base` contract](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base) -(it is a bit more complicated, but it is its core idea). +(it is a bit more complicated, but that is its core idea). -And now there is a question - how is this helpful to transfer currency if I -cannot check how much of it do I own? It is a very good question, and the +And now there is the question - how is it helpful to transfer currency if I +cannot check how much of it I own? This is a very good question, and the answer to that is simple - the whole state of every contract in our system is public. It is not universal for every Actor model, but it is how it works in CosmWasm, and it is kind of forced by the nature of blockchain. Everything -happening in blockchain has to be public, and if some information should be +happening on the blockchain has to be public, and if some information should be hidden, it has to be stored indirectly. There is one very important thing about the state in CosmWasm, and it is the @@ -160,31 +159,31 @@ updating the state), and only then do we decrease the sender amount. However, before decreasing it, we need to check if a sender possesses enough funds to perform the transaction. In case we realize that we cannot do it, we don't need to do any rolling back by hand - we would just return a failure from the action -execution, and the state would not be updated. So, when in the contract state +execution, and the state would not be updated. So, when the contract state is updated, it is just a local copy of this state being altered, but the partial changes would never be visible by other contracts. ## Queries There is one building block in the CosmWasm approach to the Actor model, which -I haven't yet cover. As I said, the whole state of every contract is public and +I haven't yet covered. As I said, the whole state of every contract is public and available for everyone to look at. The problem is that this way of looking at state is not very convenient - it requires users of contracts to know its internal structure, which kind of violates the SOLID rules (Liskov substitution -principle in particular). If, for example a contract is updated and its state +principle in particular). If, for example, a contract is updated and its state structure changes a bit, another contract looking at its state would just -nevermore work. Also, it is often the case, that the contract state is kind of +never work again. Also, it is often the case, that the contract state is kind of simplified, and information that is relevant to the observer would be calculated from the state. This is where queries come into play. Queries are the type of messages to -contract, which does not perform any actions, so do not update any state, but +contracts, which do not perform any actions, so do not update any state, but can return an answer immediately. In our KFC comparison, the query would be if Seller goes to Chef to ask "Do we still have pickles available for our cheeseburgers"? It can be done while -operating, and response can be used in it. It is possible because queries can -never update their state, so they do not need to be handled in a transactional +operating, and the response can be used in it. It is possible because queries can +never update state, so they do not need to be handled in a transactional manner. However, the existence of queries doesn't mean that we cannot look at the @@ -201,59 +200,58 @@ CosmWasm contract to visualize what the "transactional state" means. Let's imagine two contracts: 1. The `MmoCurrency` contract mentioned before, which can perform the - `Transfer` action, allows transferring some `amount` of currency to some + `Transfer` action, allowing the transfer of some `amount` of currency to some `receiver`. 2. The `WarriorNpc` contract, which would have some amount of our currency, and - he would be used by our MMO engine to pay the reward out for some quest - player could perform. It would be triggered by `Payout` action, which can be + it would be used by our MMO engine to pay the reward out for some quest + players could perform. It would be triggered by `Payout` action, which can be called only by a specific client (which would be our game engine). Now here is an interesting thing - this model forces us to make our MMO more realistic in terms of the economy that we traditionally see - it is because `WarriorNpc` has some amount of currency, and cannot create more out of -anything. It is not always the case (the previously mentioned `cw20` has a +nothing. This is not always the case (the previously mentioned `cw20` has a notion of Minting for this case), but for the sake of simplicity let's assume this is what we want. -To make the quest reasonable for longer, we would make a reward for it to be -always between `1 mmo` and `100 mmo`, but it would be ideally `15%` of what +To make the quest reasonable for longer, we would make a reward for it to +always be between `1 mmo` and `100 mmo`, but it would ideally be `15%` of what Warrior owns. This means that the quest reward decreases for every subsequent -player, until Warrior would be broke, left with nothing, and will no longer be -able to payout players. +player, until Warrior would be broke, left with nothing, and no longer able to payout players. So, what would the flow look like? The first game would send a `Payout` message to the `WarriorNpc` contract, with info on who should get the reward. Warrior would keep track of players who fulfilled the quest, to not pay out the same -person twice - there would be a list of players in his state. First, he would -check the list looking for players to pay out - if he is there, he will finish +person twice - there would be a list of players in its state. First, it would +check the list looking for the player who is due to be paid out - if he is there, it will finish the transaction with an error. However, in most cases the player would not be on the list - so then -`WarriorNpc` would add him to the list. Now the Warrior would finish his part +`WarriorNpc` would add him to the list. Now the Warrior would finish its part of the task, and schedule the `Transfer` action to be performed by `MmoCurrency`. -But there is the important thing - because `Transfer` action is actually part +But this is the important thing - because `Transfer` action is actually part of the bigger `Payout` flow, it would not be executed on the original blockchain state, but on the local copy of it, to which the player's list is -already applied to. So if the `MmoCurrency` would for any reason takes a look +already applied to. So if the `MmoCurrency` would for any reason take a look at `WarriorNpc` internal list, it would be already updated. Now `MmoCurrency` is doing its job, updating the state of Warrior and player balance (note, that our Warrior is here just treated as another player!). When it finishes, two things may happen: -1. There was an error - possibly Warrior is out of cash, and it can nevermore - pay for the task. In such case, none of the changes - neither updating the - list of players succeeding, nor balance changes are not applied to the - original blockchain storage, so they are like they never happened. In the - database world, it is denoted as rolling back the transaction. -2. Operation succeed - all changes on the state are now applied to the +1. There was an error - possibly Warrior is out of cash, and it can no longer + pay for the task. In that case, none of the changes - neither updating the + list of players, nor balance changes are applied to the + original blockchain storage, so it is like they never happened. In the + database world, this is denoted as rolling back the transaction. +2. Operation succeeds - all changes on the state are now applied to the blockchain, and any further observation of `MmoCurrency` or `WarriorNpc` by the external world would see updated data. There is one problem - in this model, our list is not a list of players who -fulfilled the quest (as we wanted it to be), but the list of players who paid +fulfilled the quest (as we wanted it to be), but the list of players who were paid out (as in transfer failure, the list is not updated). We can do better. ## Different ways of handling responses @@ -261,7 +259,7 @@ out (as in transfer failure, the list is not updated). We can do better. Note that we didn't mention a `Reply` operation at all. So why was it not called by `MmoCurrency` on `WarriorNpc`? The reason is that this operation is optional. When scheduling sub-actions on another contract we may choose when -`Reply` how the result should be handled: +`Reply` should be called and how the result should be handled: 1. Never call `Reply`, action fails if sub-message fails 2. Call `Reply` on success @@ -271,38 +269,29 @@ optional. When scheduling sub-actions on another contract we may choose when So, if we do not request `Reply` to be called by subsequent contract, it will not happen. In such a case if a sub-call fails, the whole transaction is rolled back - sub-message failure transitively causes the original message failure. It -is probably a bit complicated for now, but I promise it would be simple if you -would did some practice with that. +is probably a bit complicated for now, but I promise it will be simple when you practice a bit more. When handling the reply, it is important to remember, that although changes are -not yet applied to the blockchain (the transaction still can be failed), the +not yet applied to the blockchain (the transaction can still fail), the reply handler is already working on the copy of the state with all changes made -by sub-message so far applied. In most cases, it would be a good thing, but it +by sub-message applied. In most cases, this would be a good thing, but it has a tricky consequence - if the contract is calling itself recursively, it is possible that subsequent call overwrote things set up in the original message. -It rarely happens, but may need special treatment in some cases - for now I -don't want to go deeply into details, but I want you to remember about what to +This rarely happens, but may need special treatment in some cases - for now I +don't want to go too deeply into details, but I want you to remember what to expect after state in the actor's flow. -Now let's take a look at handling results with `2`-`4` options. It is actually -interesting, that using `2`, even if the transaction is performed by sub-call -succeed, we may now take a look at the data it returned with `Reply`, and on -its final state after it finished, and we can still decide, that act as a -whole is a failure, in which case everything would be rolled back - even -currency transfer performed by external contract. - -In our case, an interesting option is `3`. So, if the contract would call -`Reply` on failure, we can decide to claim success, and commit a transaction on -the state if the sub call failed. Why may it be relevant for us? Possibly -because our internal list was supposed to keep the list of players succeeding -with the quest, not paid out! So, if we have no more currency, we still want to -update the list! - -The most common way to use the replies (option `2` in particular) is to -instantiate another contract, managed by the one called. The idea is that in +Now let's take a look at handling results with options `2`-`4`. `2` presents an interesting case, in that even if the transaction performed by the subcall succeeds, we can still check the returned data and decide the whole transaction is a failure. In this case everything is rolled back - even the transaction performed by the external contract. + +`3` is similarly interesting in that if the external contract sends a +`Reply` on failure, we can still decide to succeed, and commit the transaction. Why would we want to do this? One use case could be if our internal list is only supposed to keep track of players succeeding +with the quest, not the ones being paid out. So, even if we have no more currency, and the external call fails, we still want to update the list! + +The most common way to use the replies (option `2` in particular) is when +instantiating another contract, managed by the one called. The idea is that in those use cases, the creator contract wants to keep the address of the created contract in its state. To do so it has to create an `Instantiate` sub-message, -and subscribe for its success response, which contains the address of the freshly +and subscribe to the success response, which contains the address of the freshly created contract. In the end, you can see that performing actions in CosmWasm is built with diff --git a/src/basics/building-contract.md b/src/basics/building-contract.md index 8db6a67..2950f6f 100644 --- a/src/basics/building-contract.md +++ b/src/basics/building-contract.md @@ -31,7 +31,7 @@ Now I can see you are disappointed in building your contracts with some overcomp instead of simple `cargo build`. Fortunately, it is not the case. The common practice is to alias the building command to make it as simple as building a native app. -Let's create the `.cargo/config` file in your contract project directory with the following content: +Let's create the `.cargo/config.toml` file in your contract project directory with the following content: ```toml [alias] diff --git a/src/basics/events.md b/src/basics/events.md index 9fef0be..71e13f3 100644 --- a/src/basics/events.md +++ b/src/basics/events.md @@ -19,7 +19,7 @@ As an example, we would add an event `admin_added` emitted by our contract on th # use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::ADMINS; use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, + to_json_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, }; # pub fn instantiate( @@ -41,8 +41,8 @@ use cosmwasm_std::{ # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # @@ -269,16 +269,14 @@ Events are emitted by adding them to the response with Additionally, there is a possibility to add attributes directly to the response. It 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 the contract. This is not always done as it adds a lot of boilerplate code to the test, and events are, generally, more like logs - not necessarily considered main contract logic. Let's now write a single test checking if execution emits events: ```rust,noplayground # use crate::error::ContractError; # use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::ADMINS; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -301,8 +299,8 @@ single test checking if execution emits events: # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # @@ -515,17 +513,19 @@ mod tests { # #[test] fn add_members() { - let mut app = App::default(); + let mut app = custom_app(); let code = ContractWrapper::new(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); + let owner = app.api().addr_make("owner"); + let addr = app .instantiate_contract( code_id, - Addr::unchecked("owner"), + owner.clone(), &InstantiateMsg { - admins: vec!["owner".to_owned()], + admins: vec![owner.to_string()], }, &[], "Contract", @@ -533,12 +533,13 @@ mod tests { ) .unwrap(); + let user = app.api().addr_make("user"); let resp = app .execute_contract( - Addr::unchecked("owner"), + owner.clone(), addr, &ExecuteMsg::AddMembers { - admins: vec!["user".to_owned()], + admins: vec![user.to_string()], }, &[], ) @@ -576,7 +577,7 @@ mod tests { .find(|attr| attr.key == "addr") .unwrap() .value, - "user" + user.to_string() ); } } diff --git a/src/basics/execute.md b/src/basics/execute.md index e94a15e..a57a8c5 100644 --- a/src/basics/execute.md +++ b/src/basics/execute.md @@ -49,7 +49,7 @@ And implement entry point: ```rust,noplayground use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::ADMINS; -# use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +# use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; # # pub fn instantiate( # deps: DepsMut, @@ -71,8 +71,8 @@ use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } @@ -313,14 +313,13 @@ edition = "2021" [lib] crate-type = ["cdylib"] -[dependencies] -cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw-storage-plus = "0.13.4" -thiserror = "1" +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +cw-storage-plus = "2.0.0" +thiserror = { version = "1.0.58" } [dev-dependencies] -cw-multi-test = "0.13.4" +cw-multi-test = "2.1.1" ``` Now we define an error type in `src/error.rs`: @@ -386,7 +385,7 @@ Now update the execute endpoint to use our new error type: use crate::error::ContractError; use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::ADMINS; -# use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +# use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; # # pub fn instantiate( # deps: DepsMut, @@ -408,8 +407,8 @@ use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } @@ -627,7 +626,7 @@ Let's write a test that verifies that a non-admin cannot add himself to a list: # use crate::error::ContractError; # use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::ADMINS; -# use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +# use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; # # pub fn instantiate( # deps: DepsMut, @@ -649,8 +648,8 @@ Let's write a test that verifies that a non-admin cannot add himself to a list: # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # diff --git a/src/basics/fp-types.md b/src/basics/fp-types.md index 1ec1bbc..25a9b95 100644 --- a/src/basics/fp-types.md +++ b/src/basics/fp-types.md @@ -7,9 +7,9 @@ The story is short: you cannot use floating-point types in smart contracts. Neve 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 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). +The biggest problem is that the 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). -This limitation has two implications. First, you always have to use decimal of fixed-point arithmetic in your contracts. +This limitation has two implications. First, you always have to use decimal or fixed-point arithmetic in your contracts. It is not 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. diff --git a/src/basics/funds.md b/src/basics/funds.md index 223d42e..35b6d2b 100644 --- a/src/basics/funds.md +++ b/src/basics/funds.md @@ -10,18 +10,13 @@ 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. -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 be transferred by their very nature. Everything that has an address on the blockchain is eligible to hold native tokens. As a consequence, tokens can be assigned to smart contracts! +Every message sent to a smart contract can have funds sent with it. In this chapter, we will take advantage of that feature and create a way to reward the hard work performed by admins. We will create a new message - Donate, which can be used by anyone to donate funds to admins, to be divided equally among 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 - +`ExecuteMsg` variant, but we will also modify the `InstantiateMsg` message a bit - we need to have some way of defining the name of a native token we would use for donations. It would be possible to allow users to send any tokens they want, but we want to simplify things for now. @@ -77,7 +72,7 @@ And instantiate it properly: # use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; use crate::state::{ADMINS, DONATION_DENOM}; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, # }; pub fn instantiate( @@ -101,8 +96,8 @@ pub fn instantiate( # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # @@ -393,22 +388,21 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] [features] library = [] [dependencies] -cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw-storage-plus = "0.13.4" -thiserror = "1" -schemars = "0.8.1" -cw-utils = "0.13" +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +cw-storage-plus = "2.0.0" +thiserror = { version = "1.0.58" } +cw-utils = "2.0.0" [dev-dependencies] -cw-multi-test = "0.13.4" -cosmwasm-schema = { version = "1.0.0" } +cw-multi-test = "2.1.1" +cosmwasm-schema = "2.1.3" ``` Then we can implement the donate handler: @@ -418,7 +412,7 @@ Then we can implement the donate handler: # use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::{ADMINS, DONATION_DENOM}; use cosmwasm_std::{ - coins, to_binary, BankMsg, Binary, Deps, DepsMut, Env, Event, MessageInfo, + coins, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, }; @@ -443,8 +437,8 @@ use cosmwasm_std::{ # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # @@ -807,7 +801,7 @@ is to write a test. # use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::{ADMINS, DONATION_DENOM}; # use cosmwasm_std::{ -# coins, to_binary, BankMsg, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, +# coins, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -831,8 +825,8 @@ is to write a test. # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # @@ -1148,22 +1142,31 @@ mod tests { # #[test] fn donations() { - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &Addr::unchecked("user"), coins(5, "eth")) - .unwrap() - }); - + let api = MockApi::default().with_prefix("inj"); + + let user = api.addr_make("user"); + + let mut app = AppBuilder::new() + .with_api(api) + .build(|router, api, storage| { + router + .bank + .init_balance(storage, &user, coins(5, "eth")) + .unwrap(); + }); + let code = ContractWrapper::new(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); - + + let test_admin1 = app.api().addr_make("admin1"); + let test_admin2 = app.api().addr_make("admin2"); + let addr = app .instantiate_contract( code_id, - Addr::unchecked("owner"), + user.clone(), // Use the properly formatted user address &InstantiateMsg { - admins: vec!["admin1".to_owned(), "admin2".to_owned()], + admins: vec![test_admin1.to_string(), test_admin2.to_string()], donation_denom: "eth".to_owned(), }, &[], @@ -1171,45 +1174,45 @@ mod tests { None, ) .unwrap(); - + app.execute_contract( - Addr::unchecked("user"), + user.clone(), addr.clone(), &ExecuteMsg::Donate {}, &coins(5, "eth"), ) .unwrap(); - + assert_eq!( app.wrap() - .query_balance("user", "eth") + .query_balance(user.as_str(), "eth") .unwrap() .amount .u128(), 0 ); - + assert_eq!( app.wrap() - .query_balance(&addr, "eth") + .query_balance(addr.as_str(), "eth") .unwrap() .amount .u128(), 1 ); - + assert_eq!( app.wrap() - .query_balance("admin1", "eth") + .query_balance(test_admin1.as_str(), "eth") .unwrap() .amount .u128(), 2 ); - + assert_eq!( app.wrap() - .query_balance("admin2", "eth") + .query_balance(test_admin2.as_str(), "eth") .unwrap() .amount .u128(), diff --git a/src/basics/good-practices.md b/src/basics/good-practices.md index 06261a0..d48b603 100644 --- a/src/basics/good-practices.md +++ b/src/basics/good-practices.md @@ -67,20 +67,25 @@ edition = "2021" [lib] crate-type = ["cdylib", "rlib"] +[features] +library = [] + [dependencies] -cosmwasm-std = { version = "1.1.4", features = ["staking"] } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw-storage-plus = "0.15.1" -thiserror = "1" -schemars = "0.8.1" -cosmwasm-schema = "1.1.4" +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +cw-storage-plus = "2.0.0" +thiserror = { version = "1.0.58" } +cw-utils = "2.0.0" +schemars = "0.8.16" +cosmwasm-schema = "2.1.3" [dev-dependencies] -cw-multi-test = "0.13.4" +cw-multi-test = "2.1.1" ``` -There is one additional change in this file - in `crate-type` I added "rlib". "cdylib" crates cannot be used as typical -Rust dependencies. As a consequence, it is impossible to create examples for such crates. +There are two additional change in this file - +1. I moved cosmwasm-schema from [dev-dependencies] to [dependencies] +2. in `crate-type` I added "rlib". "cdylib" crates cannot be used as typical 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: @@ -210,7 +215,7 @@ 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`: +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}; @@ -284,7 +289,7 @@ I encourage you to go to 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. Fortunately, we don't need to build the schema binary for the Wasm target - let's -align the `.cargo/config` file: +align the `.cargo/config.toml` file: ```toml [alias] diff --git a/src/basics/multitest-intro.md b/src/basics/multitest-intro.md index ff22ec0..b2bc60a 100644 --- a/src/basics/multitest-intro.md +++ b/src/basics/multitest-intro.md @@ -20,11 +20,11 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } [dev-dependencies] -cw-multi-test = "0.13.4" +cw-multi-test = "2.1.1" ``` I added a new @@ -37,7 +37,7 @@ When we have the dependency ready, update our test to use the framework: ```rust,noplayground # use crate::msg::{GreetResp, QueryMsg}; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -53,7 +53,7 @@ When we have the dependency ready, update our test to use the framework: # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), +# Greet {} => to_json_binary(&query::greet()?), # } # } # diff --git a/src/basics/query-testing.md b/src/basics/query-testing.md index c9e54db..70f4ddf 100644 --- a/src/basics/query-testing.md +++ b/src/basics/query-testing.md @@ -7,7 +7,7 @@ the unit test. This approach is simple and doesn't require knowledge besides Rus ```rust,noplayground # use crate::msg::{GreetResp, QueryMsg}; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -23,7 +23,7 @@ the unit test. This approach is simple and doesn't require knowledge besides Rus # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), +# Greet {} => to_json_binary(&query::greet()?), # } # } # @@ -112,7 +112,7 @@ update our test: ```rust,noplayground # use crate::msg::{GreetResp, QueryMsg}; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -128,7 +128,7 @@ update our test: # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), +# Greet {} => to_json_binary(&query::greet()?), # } # } # @@ -193,7 +193,7 @@ taking it out of nowhere. It is a lousy testing approach. We can do better: # use crate::msg::{GreetResp, QueryMsg}; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -209,7 +209,7 @@ taking it out of nowhere. It is a lousy testing approach. We can do better: # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), +# Greet {} => to_json_binary(&query::greet()?), # } # } # diff --git a/src/basics/query.md b/src/basics/query.md index 5d18c63..0e84d72 100644 --- a/src/basics/query.md +++ b/src/basics/query.md @@ -16,18 +16,18 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } [dev-dependencies] -cw-multi-test = "0.13.4" +cw-multi-test = "2.1.1" ``` Now go to your `src/lib.rs` file, and add a new query entry point: ```rust,noplayground use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, + entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use serde::{Deserialize, Serialize}; @@ -53,7 +53,7 @@ pub fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult { message: "Hello World".to_owned(), }; - to_binary(&resp) + to_json_binary(&resp) } ``` @@ -91,7 +91,7 @@ should be presented directly to the querier. Now take a look at the implementation. Nothing complicated happens there - 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. +type using the [`to_json_binary`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/fn.to_json_binary.html) function. ## Improving the message @@ -103,7 +103,7 @@ In practice, we address this by using a non-empty query message type. Improve ou ```rust,noplayground # use cosmwasm_std::{ -# entry_point, to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # use serde::{Deserialize, Serialize}; # @@ -137,7 +137,7 @@ pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { message: "Hello World".to_owned(), }; - to_binary(&resp) + to_json_binary(&resp) } } } @@ -168,7 +168,7 @@ separate function. ```rust,noplayground # use cosmwasm_std::{ -# entry_point, to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # use serde::{Deserialize, Serialize}; # @@ -197,7 +197,7 @@ pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; match msg { - Greet {} => to_binary(&query::greet()?), + Greet {} => to_json_binary(&query::greet()?), } } @@ -261,7 +261,7 @@ to be now accessed from a different module. Now move forward to the `src/contrac ```rust,noplayground use crate::msg::{GreetResp, QueryMsg}; use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, + to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; pub fn instantiate( @@ -277,7 +277,7 @@ pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; match msg { - Greet {} => to_binary(&query::greet()?), + Greet {} => to_json_binary(&query::greet()?), } } diff --git a/src/basics/rust-project.md b/src/basics/rust-project.md index a0beaee..a4779fa 100644 --- a/src/basics/rust-project.md +++ b/src/basics/rust-project.md @@ -19,7 +19,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } +cosmwasm-std = { version = "2.1.3", features = ["staking"] } ``` diff --git a/src/basics/state.md b/src/basics/state.md index a3b4c1a..6c30faf 100644 --- a/src/basics/state.md +++ b/src/basics/state.md @@ -21,12 +21,12 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -cw-storage-plus = "0.13.4" +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +cw-storage-plus = "2.0.0" [dev-dependencies] -cw-multi-test = "0.13.4" +cw-multi-test = "2.1.1" ``` Now create a new file where you will keep a state for the contract - we typically call it `src/state.rs`: @@ -108,11 +108,11 @@ pub struct InstantiateMsg { Now go forward to instantiate the entry point in `src/contract.rs`, and initialize our state to whatever we got in the instantiation message: ```rust,noplayground -# use crate::msg::{GreetResp, InstantiateMsg, QueryMsg}; +use crate::msg::{GreetResp, InstantiateMsg, QueryMsg}; use crate::state::ADMINS; // --snip-- # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # pub fn instantiate( @@ -135,7 +135,7 @@ pub fn instantiate( # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), +# Greet {} => to_json_binary(&query::greet()?), # } # } # @@ -282,7 +282,7 @@ empty JSON to some non-empty message! We can quickly fix it by updating the test # use crate::msg::{GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::ADMINS; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -305,8 +305,8 @@ empty JSON to some non-empty message! We can quickly fix it by updating the test # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # @@ -413,7 +413,7 @@ And implement it in `src/contract.rs`: use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg}; # use crate::state::ADMINS; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -436,8 +436,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; match msg { - Greet {} => to_binary(&query::greet()?), - AdminsList {} => to_binary(&query::admins_list(deps)?), + Greet {} => to_json_binary(&query::greet()?), + AdminsList {} => to_json_binary(&query::admins_list(deps)?), } } @@ -504,13 +504,13 @@ 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}; # use crate::state::ADMINS; # use cosmwasm_std::{ -# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, # }; # # pub fn instantiate( @@ -533,8 +533,8 @@ use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg}; # use QueryMsg::*; # # match msg { -# Greet {} => to_binary(&query::greet()?), -# AdminsList {} => to_binary(&query::admins_list(deps)?), +# Greet {} => to_json_binary(&query::greet()?), +# AdminsList {} => to_json_binary(&query::admins_list(deps)?), # } # } # @@ -564,13 +564,24 @@ use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg}; #[cfg(test)] mod tests { # use cosmwasm_std::Addr; -# use cw_multi_test::{App, ContractWrapper, Executor}; # # use super::*; -# +# + use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg}; + use cosmwasm_std::testing::MockApi; + use cw_multi_test::{App, ContractWrapper, Executor, AppBuilder}; + + + fn custom_app() -> App { + let mock_api = MockApi::default().with_prefix("inj"); + AppBuilder::new() + .with_api(mock_api) + .build(|_, _, _| {}) + } + #[test] fn instantiation() { - let mut app = App::default(); + let mut app = custom_app(); let code = ContractWrapper::new(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); @@ -593,12 +604,19 @@ mod tests { assert_eq!(resp, AdminsListResp { admins: vec![] }); + // Test with valid bech32 addresses + let test_admin1 = app.api().addr_make("admin1"); + let test_admin2 = app.api().addr_make("admin2"); + let addr = app .instantiate_contract( code_id, Addr::unchecked("owner"), &InstantiateMsg { - admins: vec!["admin1".to_owned(), "admin2".to_owned()], + admins: vec![ + test_admin1.to_string(), + test_admin2.to_string(), + ], }, &[], "Contract 2", @@ -614,7 +632,7 @@ mod tests { assert_eq!( resp, AdminsListResp { - admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], + admins: vec![Addr::unchecked(test_admin1), Addr::unchecked(test_admin2)], } ); } @@ -656,4 +674,6 @@ The test is simple - instantiate the contract twice with different initial admin 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. +We have had to use a mock api for the test app to ensure test addresses are deemed valid. While we are using the prefix "inj" for "injective" you can change this to be whatever you like. + We are doing a pretty good job developing our contract. Now it is time to use the state and allow for some executions. diff --git a/src/cross-contract/design.md b/src/cross-contract/design.md index 5f8dab5..5644741 100644 --- a/src/cross-contract/design.md +++ b/src/cross-contract/design.md @@ -1,12 +1,10 @@ # 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. +This time we will start by discussing the design of our system a bit. Building multi-contract systems tends to +be a bit more complicated than just isolated contracts, so I want to give you some notion of what we are +building in this chapter. If you feel lost with a design, don't worry - it will become clear while implementing the contracts. For now, go through it 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 +First, let's think about the problem we want to solve. Our admins are a vector of addresses. Anyone that is 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! This issue is relatively simple to fix, but there is another problem - as we already learned, the admin could @@ -17,17 +15,16 @@ 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 +would be able to support it. If more than half the admins support the new candidate, he will immediately become an admin. It is not the most convoluted voting process, but it would be enough for our 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. First, we will reuse the contract from the +[Basics](../basics.md) chapter - this will be the `admin` contract. Additionally, we will add a `voting` contract. +The voting contract will 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: ```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 an admin flowchart - this assumes there are 5 admins on the contract already, but 2 of them do nothing: ```plantuml @startuml @@ -86,7 +83,7 @@ votes -> admin --: add_admin { addr: new_admin } @enduml ``` -I already put some hints about contracts implementation, but I will not go into them yet. +I already put some hints about the contract implementations, but I will not go into them yet. ## Messages forwarding diff --git a/src/cross-contract/fixing-admin.md b/src/cross-contract/fixing-admin.md index d53b7dc..c0e758b 100644 --- a/src/cross-contract/fixing-admin.md +++ b/src/cross-contract/fixing-admin.md @@ -1,7 +1,7 @@ # 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 +contract we already have to become an admin contract. It is mostly fine at this point, but we want to do a cleanup. ## Cleaning up queries @@ -47,7 +47,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; match msg { - AdminsList {} => to_binary(&query::admins_list(deps)?), + AdminsList {} => to_json_binary(&query::admins_list(deps)?), } } ``` @@ -56,49 +56,16 @@ 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). -## 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, -defined messages. - -Good for us. It is easy to fix. You might notice 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 -dependency. Let's update our `Cargo.toml`: +## Change contract name +"contract" is not very descriptive, so lets update the name in the Cargo.toml to "admin". You may need to update the import in schema.rs as well. ```toml [package] name = "admin" version = "0.1.0" edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] -# -# [features] -# library = [] -# -# [dependencies] -# cosmwasm-std = { version = "1.1.4", features = ["staking"] } -# serde = { version = "1.0.103", default-features = false, features = ["derive"] } -# cw-storage-plus = "0.15.1" -# thiserror = "1" -# schemars = "0.8.1" -# cw-utils = "0.15.1" -# cosmwasm-schema = "1.1.4" -# -# [dev-dependencies] -# cw-multi-test = "0.15.1" ``` -Also, note I changed the contract name - "contract" is not very descriptive, so -I updated it to "admin". - ## Project structure Last but not least - we want to better structure our project. So far, we have diff --git a/src/cross-contract/map-storage.md b/src/cross-contract/map-storage.md index eb62da6..6a393c5 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 that can immediately be improved in the admin contract. Let's check the contract state: ```rust @@ -11,20 +11,18 @@ 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. +Note that we keep our admin list as a single vector. However, 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 +This is not ideal, as now, whenever we want to access a single admin entry, +we have to firstdeserialize the entire list and then iterate over it until we find the admin we are interested in. This might consume a serious +amount of gas and is completely unnecessary - we can avoid that using the [Map](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html) storage accessor. ## The `Map` storage 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. +assigned to them, just like a `HashMap` in Rust or `dictionaries` in many other languages. We define it as similar to an `Item`, but this time we need two types - the key type and the value type: @@ -89,7 +87,7 @@ First, you might wonder about extra values passed to 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. +elements should be traversed in. While working with typical Rust iterators, you would probably first create an iterator over all the elements and then somehow skip those you are not @@ -98,23 +96,21 @@ interested in. After that, you will 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 +we try to avoid as much as possible. One way to do this is to instruct the Map where 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, -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 +Although its rare, it can happen that an item which is supposed to exist, throws an error while being read from storage - perhaps the stored value is serialized in an unexpected way, and deserialization fails. This is actually a real thing that happened in one of the contracts I 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 I imagine you can call me crazy right now - why do I keep going on about a `Map`, while +we are working with a vector? It is clear that those two represent two distinct things! Or do they? Let's reconsider what we keep in the `ADMINS` vector - we have a list of objects @@ -122,11 +118,11 @@ 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: > 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. +> values assigned to them, just like a HashMap in Rust or dictionaries in many other 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 +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 @@ -168,7 +164,7 @@ pub fn instantiate( } ``` -It didn't simplify much, but we no longer need to collect our address. Then +It didn't simplify much, but we no longer need to collect our addresses. Now let's move to the leaving logic: ```rust @@ -196,11 +192,10 @@ 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 +gas-consuming when using a Map! In general, we tend 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 -any gas limit. +many dummy elements in the a vector, the execution cost may exceed the gas limits and make the contract unuseable. Unfortunately, we have such an iteration in our contract - the distribution flow becomes as follows: @@ -238,9 +233,9 @@ pub fn donate(deps: DepsMut, info: MessageInfo) -> Result>` here. -Fortunately, it is not the case - the distribution does not have to be linear in +Fortunately, this 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 @@ -263,16 +258,8 @@ 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 - -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 -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, -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. +First, queries are often intended to be called on local nodes, with no gas cost - we can query contracts as much as we want. +Additionally, even if we 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 by adding pagination - to limit the execution time/cost, the query caller would be able to ask for a limited number of items starting from a given one. Given your knowledge from this chapter, you can probably figure out the implementation now, but I will show the common way we do that when I go through common CosmWasm practices. ## Reference keys diff --git a/src/cross-contract/working-with-time.md b/src/cross-contract/working-with-time.md index 0457e83..b4515f9 100644 --- a/src/cross-contract/working-with-time.md +++ b/src/cross-contract/working-with-time.md @@ -4,11 +4,11 @@ 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. -However, there is the notion of a time that is even +However, there is the notion of a time that is 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. +the entire block, which is made up of multiple transactions. The time is encoded in the [`Env`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.Env.html) @@ -28,9 +28,9 @@ 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 -between blocks, while two blocks may be executed with the -same `time` (even though it is rather not probable). +guaranteed that the `height` field increases +between blocks, while two blocks may be executed within the +same `time` (even though it 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 @@ -66,20 +66,11 @@ 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 -assign the join time to every admin. +As you can see, our admins set 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: +You might argue that we should create a separate structure for the value of this map, so that in the future, if we need to add something there, we can. However, in my opinion, this would be premature optimization. We can also change the entire value type in the future, as it would be the same breaking change. -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. - -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 the map. We previously stored Empty data, but it no longer matches our value type. Let's examine the updated instantiation function: ```rust use crate::state::{ADMINS, DONATION_DENOM}; @@ -106,7 +97,7 @@ 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 +`env` block - it was there to tell the Rust compiler the variable is purposely unused and not some kind of a bug. Finally, remember to remove any obsolete `Empty` imports @@ -134,23 +125,8 @@ 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 -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 -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 -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 -which would be simulated on the call. +You may ask why I suggest always returning a joined value in the response, and what to do when no such admin is added. Well, in such a case, I would rely on the fact that the load function returns a descriptive error of a missing value in storage. However, feel free to define your own error for such a case or even make the joined field optional, to be returned only if the requested admin exists. + +Finally, it would be a good idea to create a test for the new functionality. Call the new query right after instantiation to verify that initial admins have the proper join time (possibly by extending the existing instantiation test). + +One thing you might need help with in tests is how to get the time of execution. Using any OS-time would 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 access 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 ensure you are working with the same state that would be simulated on the call. diff --git a/src/wasmd-quick-start.md b/src/wasmd-quick-start.md index a629cb3..8cab225 100644 --- a/src/wasmd-quick-start.md +++ b/src/wasmd-quick-start.md @@ -6,3 +6,4 @@ real environment is to use one of the big CosmWasm chains testnets - Osmosis, Ju Terra, or other ones. [Here](https://docs.osmosis.zone/cosmwasm/testnet/cosmwasm-deployment/) is the recommended introduction on how to start with the Osmosis testnet. +[Here](https://docs.cosmwasm.com/wasmd) is the setup for wasmd From 9a5abbe14d5d82857951fd2c6fedb11b5139c78d Mon Sep 17 00:00:00 2001 From: tiffgerst Date: Mon, 16 Sep 2024 10:16:58 +0100 Subject: [PATCH 2/3] fix(book):add_members fixes, fix tests --- src/basics/events.md | 2 +- src/basics/state.md | 10 +---- src/cross-contract/map-storage.md | 54 ++++++++++++++++++++++++- src/cross-contract/working-with-time.md | 35 ++++++++++++++++ 4 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/basics/events.md b/src/basics/events.md index 71e13f3..9517665 100644 --- a/src/basics/events.md +++ b/src/basics/events.md @@ -513,7 +513,7 @@ mod tests { # #[test] fn add_members() { - let mut app = custom_app(); + let mut app = App::default(); let code = ContractWrapper::new(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); diff --git a/src/basics/state.md b/src/basics/state.md index 6c30faf..e370fda 100644 --- a/src/basics/state.md +++ b/src/basics/state.md @@ -571,17 +571,9 @@ mod tests { use cosmwasm_std::testing::MockApi; use cw_multi_test::{App, ContractWrapper, Executor, AppBuilder}; - - fn custom_app() -> App { - let mock_api = MockApi::default().with_prefix("inj"); - AppBuilder::new() - .with_api(mock_api) - .build(|_, _, _| {}) - } - #[test] fn instantiation() { - let mut app = custom_app(); + let mut app = App::default(); let code = ContractWrapper::new(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); diff --git a/src/cross-contract/map-storage.md b/src/cross-contract/map-storage.md index 6a393c5..fbfee17 100644 --- a/src/cross-contract/map-storage.md +++ b/src/cross-contract/map-storage.md @@ -241,7 +241,7 @@ 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. -The final function to fix is the `admins_list` query handler: +The final functions to fix are the `admins_list` query handler and the `add_members` function: ```rust use crate::state::ADMINS; @@ -255,12 +255,43 @@ pub fn admins_list(deps: Deps) -> StdResult { let resp = AdminsListResp { admins }; Ok(resp) } + ``` Here we also have an issue with linear complexity, but it is far less of a problem. First, queries are often intended to be called on local nodes, with no gas cost - we can query contracts as much as we want. Additionally, even if we 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 by adding pagination - to limit the execution time/cost, the query caller would be able to ask for a limited number of items starting from a given one. Given your knowledge from this chapter, you can probably figure out the implementation now, but I will show the common way we do that when I go through common CosmWasm practices. +Finally, update the add_members function + +```rust +pub fn add_members( + deps: DepsMut, + info: MessageInfo, + admins: Vec, + ) -> Result { + if !ADMINS.has(deps.storage, info.sender.clone()) { + return Err(ContractError::Unauthorized { + sender: info.sender, + }); + } + let events = admins + .iter() + .map(|admin| Event::new("admin_added").add_attribute("addr", admin)); + let resp = Response::new() + .add_events(events) + .add_attribute("action", "add_members") + .add_attribute("added_count", admins.len().to_string()); + + for addr in admins { + let admin = deps.api.addr_validate(&addr)?; + ADMINS.save(deps.storage, admin, &Empty {})?; + } + + Ok(resp) + } +``` + ## Reference keys There is one subtlety to improve in our map usage. @@ -278,7 +309,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 usages of the map in a few places: ```rust # use crate::state::{ADMINS, DONATION_DENOM}; @@ -315,4 +346,23 @@ pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { # Ok(resp) } + +pub fn add_members( + deps: DepsMut, + info: MessageInfo, + admins: Vec, + ) -> Result { + if !ADMINS.has(deps.storage,&info.sender) { + return Err(ContractError::Unauthorized { + sender: info.sender, + }); + } + + for addr in admins { + let admin = deps.api.addr_validate(&addr)?; + ADMINS.save(deps.storage, &admin, &Empty {})?; + } + + Ok(resp) + } ``` diff --git a/src/cross-contract/working-with-time.md b/src/cross-contract/working-with-time.md index b4515f9..e2643a8 100644 --- a/src/cross-contract/working-with-time.md +++ b/src/cross-contract/working-with-time.md @@ -100,6 +100,41 @@ note that I removed the underscore from the name of the `env` block - it was there to tell the Rust compiler the variable is purposely unused and not some kind of a bug. +Similarly, we need to update the add_members function, and the way we are calling the function: + +```rust +pub fn add_members( + deps: DepsMut, + env: Env, + info: MessageInfo, + admins: Vec, + ) -> Result { + + for addr in admins { + let admin = deps.api.addr_validate(&addr)?; + ADMINS.save(deps.storage, &admin, &env.block.time)?; + } + + Ok(resp) + } + + pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + use ExecuteMsg::*; + + match msg { + AddMembers { admins } => execute::add_members(deps, env, info, admins), + Leave {} => execute::leave(deps, info).map_err(Into::into), + Donate {} => execute::donate(deps, info), + } +} +``` +Note that we now need to use and pass in Env to the add_members function as we need to access the block timestamp. + Finally, remember to remove any obsolete `Empty` imports through the project - the compiler should help you point out unused imports. From 5c466266c2ac6af8824c2bd11d793a70d7bd6b31 Mon Sep 17 00:00:00 2001 From: tiffgerst Date: Mon, 16 Sep 2024 10:33:40 +0100 Subject: [PATCH 3/3] fix(book):minor fixes --- src/actor-model/actors-in-blockchain.md | 2 +- src/basics/funds.md | 2 ++ src/basics/state.md | 1 - src/cross-contract/working-with-time.md | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/actor-model/actors-in-blockchain.md b/src/actor-model/actors-in-blockchain.md index 9f14101..f7cf9fd 100644 --- a/src/actor-model/actors-in-blockchain.md +++ b/src/actor-model/actors-in-blockchain.md @@ -3,7 +3,7 @@ Previously we were talking about actors mostly in the abstraction of any blockchain-specific terms. However, before we can dive into the code, we need to establish some common language. To do so we need to look at contracts from -the perspective of external users instead of their implementation. +the perspective of external users, instead of their implementation. In this part, I will use the `wasmd` binary to communicate with a testnet. To properly set it up, check the [Quick start with diff --git a/src/basics/funds.md b/src/basics/funds.md index 35b6d2b..46bb466 100644 --- a/src/basics/funds.md +++ b/src/basics/funds.md @@ -1245,6 +1245,8 @@ where the [`init_balance`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.BankKeeper.html#method.init_balance) function sits. +Not that we have also had to use a mock api for the test app to ensure test addresses are deemed valid. While we are using the prefix "inj" for "injective" you can change this to be whatever you like. + ## Plot Twist! As we covered most of the important basics about building Rust smart contracts, I have a serious exercise for you. diff --git a/src/basics/state.md b/src/basics/state.md index e370fda..c8ba070 100644 --- a/src/basics/state.md +++ b/src/basics/state.md @@ -666,6 +666,5 @@ The test is simple - instantiate the contract twice with different initial admin 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. -We have had to use a mock api for the test app to ensure test addresses are deemed valid. While we are using the prefix "inj" for "injective" you can change this to be whatever you like. We are doing a pretty good job developing our contract. Now it is time to use the state and allow for some executions. diff --git a/src/cross-contract/working-with-time.md b/src/cross-contract/working-with-time.md index e2643a8..88141cc 100644 --- a/src/cross-contract/working-with-time.md +++ b/src/cross-contract/working-with-time.md @@ -4,7 +4,7 @@ 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. -However, there is the notion of a time that is +However, there is the notion of time that is 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